Como a KAYAK reduziu em 50% o tempo de login e reforçou a segurança com as chaves de acesso

OUT 24, 2023
Kateryna Semenova Developer Relations Engineer, Android

Introdução

O KAYAK é um dos principais mecanismos de pesquisa de viagens do mundo e ajuda os usuários a encontrar as melhores ofertas em voos, hotéis e aluguel de veículos. Em 2023, o KAYAK integrou as chaves de acesso, um novo tipo de autenticação sem senha, a seus apps Android e da Web. Como resultado, o KAYAK reduziu em 50% o tempo médio que os usuários levam para se cadastrar e fazer login e também reduziu o volume de tíquetes de suporte.

Este estudo de caso explica a implementação do KAYAK no Android com a API Credential Manager e o RxJava. Você pode usar o estudo como um modelo para implementar o Credential Manager a fim de reforçar a segurança e melhorar a experiência do usuário em seus próprios apps.

Para ver um resumo rápido, confira o vídeo complementar no YouTube.

Link to Youtube Video (visible only when JS is disabled)

Problema

Assim como a maioria das empresas, a KAYAK sempre usou as senhas para autenticar usuários. As senhas podem ser um risco tanto para usuários quanto para empresas: elas geralmente são fracas, reutilizadas, facilmente adivinhadas, roubadas, vazadas ou hackeadas.

"A oferta da autenticação por senha acarreta muito trabalho e risco para as empresas. Os invasores estão constantemente tentando acessar ar contas à força, embora nem todos os usuários entendam a necessidade de usar senhas fortes. No entanto, mesmo senhas fortes não são totalmente seguras e ainda podem ser alvo de phishing." – Matthias Keller, cientista-chefe e vice-presidente sênior de tecnologia da KAYAK

Para tornar a autenticação mais segura, o KAYAK enviava "links mágicos" por e-mail. Embora seja útil do ponto de vista da segurança, essa etapa extra introduzia mais atrito para os usuários, exigindo que eles mudassem para outro aplicativo a fim de concluir o processo de login. Medidas adicionais precisavam ser introduzidas para mitigar o risco de ataques de phishing.

Solução

O app Android do KAYAK agora usa chaves de acesso para oferecer uma experiência de autenticação mais segura, rápida e fácil de usar. As chaves de acesso são tokens exclusivos e seguros que são armazenados no dispositivo do usuário e podem ser sincronizados em vários dispositivos. Os usuários podem fazer login no KAYAK com uma chave de acesso simplesmente usando o bloqueio de tela do dispositivo existente, o que é mais simples e seguro do que inserir uma senha.

"Adicionamos o suporte a chaves de acesso ao nosso app Android para que mais usuários possam usar essas chaves em vez de senhas. Dentro desse trabalho, também substituímos nossa antiga implementação da API Smartlock pelo recurso Fazer login com o Google, suportado pela API Credential Manager. Agora, os usuários podem se cadastrar e fazer login no KAYAK com as chaves de acesso duas vezes mais rápido do que com um link de e-mail, o que também eleva a taxa de conclusão." – Matthias Keller, cientista-chefe e vice-presidente sênior de tecnologia da KAYAK

Integração da API Credential Manager

Para integrar as chaves de acesso ao Android, o KAYAK usou a API Credential Manager. O Credential Manager é uma biblioteca do Jetpack que unifica o suporte a chaves de acesso a partir do Android 9 (API nível 28) e o suporte a métodos tradicionais de login, como senhas e autenticação federada, em uma única interface do usuário e API.

kayak2
Figura 1: Tela de criação de chave de acesso do Credential Manager.

Projetar um fluxo de autenticação robusto para apps é crucial para garantir a segurança e uma experiência do usuário confiável. O diagrama a seguir demonstra como o KAYAK integrou as chaves de acesso a seus fluxos de registro e autenticação:

kayak3
Figura 2: Diagrama do KAYAK mostrando seus fluxos de registro e autenticação.

No momento do registro, os usuários têm a oportunidade de criar uma chave de acesso. Uma vez registrados, eles podem fazer login usando a chave de acesso, Fazer login com o Google ou usar uma senha. Como o Credential Manager inicia a IU automaticamente, tenha cuidado para não introduzir tempos de espera inesperados, como chamadas de rede. Sempre busque um desafio único e outras configurações de chaves de acesso (como ID de RP) no início de qualquer sessão do app.

Embora a equipe do KAYAK agora esteja fortemente voltada para as corrotinas, a integração inicial usou o RxJava para a integração à API Credential Manager. Eles agruparam as chamadas do Credential Manager no RxJava da seguinte forma:

override fun createCredential(request: CreateCredentialRequest, activity: Activity): Single<CreateCredentialResponse> {
   return Single.create { emitter ->
       // Triggers credential creation flow
       credentialManager.createCredentialAsync(
           request = request,
           activity = activity,
           cancellationSignal = null,
           executor = Executors.newSingleThreadExecutor(),
           callback = object : CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> {
               override fun onResult(result: CreateCredentialResponse) {
                   emitter.onSuccess(result)
               }
 
               override fun onError(e: CreateCredentialException) {
                   emitter.tryOnError(e)
               }
           }
       )
   }
}

Este exemplo define uma função do Kotlin chamada createCredential() que retorna uma credencial do usuário como uma classe Single do RxJava do tipo CreateCredentialResponse. A função createCredential() encapsula o processo assíncrono de registro de credenciais em um estilo de programação reativo usando a classe Single do RxJava.

Para ver uma implementação do Kotlin desse processo usando corrotinas, leia o guia Fazer login do usuário com o Credential Manager.

Fluxo de registro de novos usuários

Este exemplo demonstra a abordagem que o KAYAK usou para registrar uma nova credencial. Aqui, o Credential Manager foi agrupado em primitivas Rx.

webAuthnRetrofitService
  .getClientParams(username = /** email address **/)
  .flatMap { response ->
      // Produce a passkeys request from client params that include a one-time challenge
      CreatePublicKeyCredentialOption(/** produce JSON from response **/)
  }
  .subscribeOn(schedulers.io())
  .flatMap { request ->
      // Call the earlier defined wrapper which calls the Credential Manager UI
      // to register a new passkey credential
      credentialManagerRepository
      .createCredential(
           request = request,
           activity = activity
       )
  }
  .flatMap {
     // send credential to the authentication server
  }
  .observeOn(schedulers.main())
  .subscribe(
      { /** process successful login, update UI etc. **/ },
      { /** process error, send to logger **/ }
  )

O Rx permitiu que o KAYAK produzisse pipelines mais complexos, que podem envolver várias interações com o Credential Manager.

Login de usuários existentes

O KAYAK usou as etapas a seguir para iniciar o fluxo de login. O processo inicia um elemento da IU de página inferior, permitindo que o usuário faça login usando um ID do Google e uma chave de acesso ou senha salva existente.

kayak4
Figura 3: Página inferior para autenticação de chave de acesso.

Os desenvolvedores devem seguir estas etapas para configurar um fluxo de login:

  1. Como a página inferior é iniciada automaticamente, tenha cuidado para não introduzir tempos de espera inesperados na IU, como chamadas de rede. Sempre busque um desafio único e outras configurações de chaves de acesso (como ID de RP) no início de qualquer sessão do app.
  2. Ao oferecer o login com o Google por meio da API Credential Manager, seu código deve buscar inicialmente as contas do Google que já foram usadas com o app. Para lidar com isso, chame a API com o parâmetro setFilterByAuthorizedAccounts definido como true.
  3. Se o resultado retornar uma lista de credenciais disponíveis, o app mostrará a IU de autenticação da página inferior ao usuário.
  4. Se uma NoCredentialException aparecer, nenhuma credencial foi encontrada: nenhuma conta do Google, nenhuma chave de acesso e nenhuma senha salva. Nesse ponto, o app deve chamar a API novamente e definir setFilterByAuthorizedAccounts como false para iniciar o fluxo Cadastrar-se com o Google.
  5. Processe a credencial retornada pelo Credential Manager.
Single.fromSupplier<GetPublicKeyCredentialOption> {
  GetPublicKeyCredentialOption(/** Insert challenge and RP ID that was fetched earlier **/)
}
  .flatMap { response ->
      // Produce a passkeys request
      GetPublicKeyCredentialOption(response.toGetPublicKeyCredentialOptionRequest())
  }
  .subscribeOn(schedulers.io())
  .map {  publicKeyCredentialOption ->
    // Merge passkeys request together with other desired options,
    // such as Google sign-in and saved passwords.
  }
  .flatMap { request ->
    // Trigger Credential Manager system UI
    credentialManagerRepository.getCredential(
      request = request,
      activity = activity
    )
  }
  .onErrorResumeNext { throwable ->
      // When offering Google sign-in, it is recommended to first only look for Google accounts
      // that have already been used with our app. If there are no such Google accounts, no passkeys,
      // and no saved passwords, we try looking for any Google sign-in one more time.
      if (throwable is NoCredentialException) {
        return@onErrorResumeNext credentialManagerRepository.getCredential(
          request = GetCredentialRequest(/* Google ID with filterByAuthorizedOnly = false */),
          activity = activity
        )
      }
    Single.error(throwable)
  }
  .flatMapCompletable {
    // Step 1: Use Retrofit service to send the credential to the server for validation. Waiting
    // for the server is handled on a IO thread using subscribeOn(schedulers.io()).
    // Step 2: Show the result in the UI. This includes changes such as loading the profile
    // picture, updating to the personalized greeting, making member-only areas active,
    // hiding the sign-in dialog, etc. The activities of step 2 are executed on the main thread.
  }
  .observeOn(schedulers.main())
  .subscribe(
    // Handle errors, e.g. send to log ingestion service. 
    // A subset of exceptions shown to the user can also be helpful,
    // such as user setup problems. 
    // Check out more info in Troubleshoot common errors at
    // https://developer.android.com/training/sign-in/passkeys#troubleshoot
  )
"Com a implementação geral da API Credential Manager, fica muito fácil adicionar outros métodos de autenticação. A adição do login com um toque do Google exigiu pouquíssimo trabalho após a adição das chaves de acesso." – Matthias Keller

Para saber mais, siga o guia sobre como integrar a API Credential Manager e como integrar o Credential Manager com o recurso Fazer login com o Google.

Considerações de UX

Uma das principais considerações de experiência do usuário que o KAYAK enfrentou ao mudar para as chaves de acesso foi se os usuários deveriam ser capazes de excluir chaves de acesso ou criar mais de uma chave.

Nosso guia de UX para chaves de acesso recomenda que haja uma opção para revogar uma chave de acesso e que você garanta que o usuário não crie chaves de acesso duplicadas para o mesmo nome de usuário no mesmo gerenciador de senhas.

kayak5
Figura 4: IU do KAYAK para o gerenciamento de chaves de acesso.

Para evitar o registro de várias credenciais para a mesma conta, o KAYAK usou a propriedade excludeCredentials, que lista as credenciais já registradas para o usuário. O exemplo a seguir demonstra como criar novas credenciais no Android sem duplicatas:

fun WebAuthnClientParamsResponse.toCreateCredentialRequest(): String {
        val credentialRequest = WebAuthnCreateCredentialRequest(
            challenge = this.challenge!!.asSafeBase64,
            relayingParty = this.relayingParty!!,
            pubKeyCredParams = this.pubKeyCredParams!!,
            userEntity = WebAuthnUserEntity(
              id = this.userEntity!!.id.asSafeBase64,
                   name = this.userEntity.name,
                   displayName = this.userEntity.displayName
            ),
            authenticatorSelection = WebAuthnAuthenticatorSelection(
                  authenticatorAttachment = "platform",
                  residentKey = "preferred"
             ),
            // Setting already existing credentials here prevents
            // creating multiple passkeys on the same keychain/password manager
            excludeCredentials = this.allowedCredentials!!.map { it.copy(id = it.id.asSafeBase64) },
        )
        return GsonBuilder().disableHtmlEscaping().create().toJson(credentialRequest)
}

E foi assim que o KAYAK implementou a funcionalidade excludeCredentials para sua implementação na Web:

var registrationOptions = {
   'publicKey': {
       'challenge': self.base64ToArrayBuffer(data.challenge),
       'rp': data.rp,
       'user': {
           'id': new TextEncoder().encode(data.user.id),
           'name': data.user.name,
           'displayName': data.user.displayName
       },
       'pubKeyCredParams': data.pubKeyCredParams,
       'authenticatorSelection': {
           'residentKey': 'required'
       }
   }
};
 
if (data.allowCredentials && data.allowCredentials.length > 0) {
   var excludeCredentials = [];
   for (var i = 0; i < data.allowCredentials.length; i++) {
       excludeCredentials.push({
           'id': self.base64ToArrayBuffer(data.allowCredentials[i].id),
           'type': data.allowCredentials[i].type
       });
   }
   registrationOptions.publicKey.excludeCredentials = excludeCredentials;
}
 
navigator.credentials.create(registrationOptions);

Implementação do lado do servidor

O lado do servidor é um componente essencial de uma solução de autenticação. O KAYAK adicionou recursos de chave de acesso ao back-end de autenticação existente utilizando a biblioteca Java de código aberto WebAuthn4J.

O KAYAK dividiu o processo do lado do servidor nas seguintes etapas:

  1. O cliente solicita os parâmetros necessários para criar ou usar uma chave de acesso ao servidor. Isso inclui o desafio, o algoritmo de criptografia suportado, o ID da parte confiável e os itens relacionados. Se o cliente já tiver um endereço de e-mail do usuário, os parâmetros incluirão o objeto do usuário para registro e uma lista de chaves de acesso, se houver.
  2. O cliente executa fluxos de navegador ou app para iniciar o registro ou o login com a chave de acesso.
  3. O cliente envia as informações de credencial recuperadas para o servidor. Isso inclui ID do cliente, dados do autenticador, dados do cliente e outros itens relacionados. Essas informações são necessárias para criar uma conta ou verificar um login.

Quando a KAYAK trabalhou nesse projeto, nenhum produto de terceiros dava suporte a chaves de acesso. No entanto, muitos recursos agora estão disponíveis para criar um servidor de chaves de acesso, o que inclui documentação e exemplos de biblioteca.

Resultados

Desde a integração das chaves de acesso, a KAYAK registrou um aumento significativo na satisfação do usuário. Os usuários relataram que acham as chaves de acesso muito mais fáceis de usar do que as senhas, pois não exigem que eles memorizem ou digitem uma sequência longa e complexa de caracteres. A KAYAK reduziu em 50% o tempo médio que os usuários levavam para se cadastrar e fazer login, notou uma redução do volume de tíquetes de suporte relacionados a senhas esquecidas e tornou seu sistema mais seguro, reduzindo a exposição a ataques baseados em senhas. Graças a essas melhorias, a KAYAK planeja eliminar a autenticação baseada em senha de seu app até o final de 2023.

"As chaves de acesso tornam a criação de uma conta extremamente rápida, removendo a necessidade de criar senhas ou navegar para um aplicativo separado para obter um link ou código. Como um bônus, a implementação da nova biblioteca do Credential Manager também reduziu o débito técnico em nossa base de código, colocando chaves de acesso, senhas e login com o Google em uma única IU nova e moderna. Na verdade, os usuários podem se cadastrar e fazer login no KAYAK com chaves de acesso duas vezes mais rápido do que com um link de e-mail, o que também eleva a taxa de conclusão." – Matthias Keller

Conclusão

As chaves de acesso são uma solução de autenticação nova e inovadora que oferece benefícios significativos em relação às senhas tradicionais. O KAYAK é um ótimo exemplo de como uma organização pode melhorar a segurança e a usabilidade do processo de autenticação integrando chaves de acesso. Se você busca uma experiência de autenticação mais segura e fácil de usar, recomendamos que considere o uso de chaves de acesso com a API Credential Manager do Android.