Fazer login com uma chave de acesso usando o preenchimento automático de formulários

Crie uma experiência de login que use chaves de acesso e ainda acomode usuários de senhas atuais.

Este guia explica como usar o preenchimento automático de formulários para permitir que os usuários façam login com chaves de acesso e senhas. O uso do preenchimento automático de formulário cria uma experiência de login unificada, simplificando a transição de senhas para o método de autenticação de chave de acesso mais seguro e fácil de usar.

Aprenda a implementar a interface condicional da WebAuthn para oferecer suporte a usuários de chaves de acesso e senhas com o mínimo de atrito nos seus formulários de login atuais.

Por que usar o preenchimento automático de formulários para fazer login com uma chave de acesso?

Com as chaves de acesso, os usuários podem fazer login em sites usando a impressão digital, o rosto ou o PIN do dispositivo.

Se todos os usuários tivessem chaves de acesso, o fluxo de autenticação poderia ser um único botão de login. Ao tocar no botão, o usuário pode verificar a conta diretamente com o bloqueio de tela e fazer login.

No entanto, a transição de senhas para chaves de acesso apresenta desafios. Os sites precisam oferecer suporte a usuários de senhas e chaves de acesso durante esse período. Esperar que os usuários se lembrem de quais sites usam chaves de acesso e pedir que escolham um método de login antecipadamente cria uma experiência ruim para eles.

As chaves de acesso também são uma tecnologia nova, e explicá-las com clareza pode ser difícil. Usar a interface de preenchimento automático familiar ajuda a resolver o desafio da transição e a necessidade de familiaridade do usuário.

Usar a interface condicional

Para oferecer suporte eficiente a usuários de chaves de acesso e senhas, inclua chaves de acesso nas sugestões de preenchimento automático do formulário. Essa abordagem usa a interface condicional, um recurso do padrão WebAuthn.

Exemplo de seleção de chave de acesso usando o preenchimento automático de formulários.

Quando o usuário se concentra no campo de entrada de nome de usuário, uma caixa de diálogo de preenchimento automático aparece, sugerindo chaves de acesso armazenadas junto com senhas salvas. O usuário pode selecionar uma chave de acesso ou uma senha e fazer login usando o bloqueio de tela do dispositivo se escolher uma chave de acesso.

Isso permite que os usuários façam login no seu site com o formulário de login atual, mas com o benefício adicional de segurança das chaves de acesso se eles tiverem uma.

Como funciona a autenticação por chave de acesso

Para autenticar com uma chave de acesso, use a API WebAuthn.

Os quatro componentes em um fluxo de autenticação de chave de acesso são:

  • Back-end: armazena detalhes da conta do usuário, incluindo a chave pública.
  • Front-end: se comunica com o navegador e busca os dados necessários do back-end.
  • Navegador: executa seu JavaScript e interage com a API WebAuthn.
  • Provedor de chave de acesso: cria e armazena a chave de acesso. Normalmente, é um gerenciador de senhas, como o Gerenciador de senhas do Google, ou uma chave de segurança.
Fluxo de autenticação da chave de acesso mostrando a interação entre front-end, back-end, navegador e provedor de chave de acesso.
Fluxo completo de autenticação de chaves de acesso.

O processo de autenticação de chaves de acesso segue este fluxo:

  1. O usuário acessa a página de login, e o front-end solicita um desafio de autenticação do back-end.
  2. O back-end gera e retorna um desafio da WebAuthn associado à conta do usuário.
  3. O front-end chama navigator.credentials.get() com o desafio para iniciar a autenticação usando o navegador.
  4. O navegador, interagindo com o provedor de chaves de acesso, pede ao usuário para selecionar uma chave de acesso (geralmente usando uma caixa de diálogo de preenchimento automático acionada ao focar no campo de login) e verificar a identidade usando o bloqueio de tela ou a biometria do dispositivo.
  5. Depois da verificação bem-sucedida do usuário, o provedor de chaves de acesso assina o desafio, e o navegador retorna a credencial de chave pública resultante (incluindo a assinatura) para o front-end.
  6. O front-end envia essa credencial ao back-end.
  7. O backend verifica a assinatura da credencial com a chave pública armazenada do usuário. Se a verificação for bem-sucedida, o back-end fará login do usuário.

Autenticar com uma chave de acesso usando o preenchimento automático de formulários

Para iniciar a autenticação por chaves de acesso usando o preenchimento automático de formulários, faça uma chamada condicional WebAuthn get quando a página de login for carregada. Essa chamada para navigator.credentials.get() inclui a opção mediation: 'conditional'.
Uma solicitação condicional para a API navigator.credentials.get() WebAuthn não mostra a interface imediatamente. Em vez disso, ele aguarda em um estado pendente até que o usuário interaja com o campo de nome de usuário do preenchimento automático. Se o usuário selecionar uma chave de acesso, o navegador vai resolver a promessa pendente com uma credencial para fazer login, ignorando o envio tradicional de formulário. Se o usuário escolher uma senha, a promessa não será resolvida, e o fluxo padrão de login com senha vai continuar.rm. Então, é responsabilidade da página fazer login do usuário.

Anotar campo de entrada do formulário

Para ativar o preenchimento automático de chaves de acesso, adicione o atributo autocomplete ao campo nome de usuário input do formulário. Inclua username e webauthn como valores separados por espaços.

<input type="text" name="username" autocomplete="username webauthn" autofocus>

Adicionar autofocus a esse campo aciona automaticamente o preenchimento automático no carregamento de página, mostrando senhas e chaves de acesso disponíveis imediatamente.

Detecção de recursos

Antes de invocar uma chamada de API condicional da WebAuthn, verifique se:

  • O navegador é compatível com o WebAuthn com PublicKeyCredential.

Browser Support

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Source

Browser Support

  • Chrome: 133.
  • Edge: 133.
  • Firefox: 135.
  • Safari: 17.4.

Source

O snippet a seguir mostra como verificar se o navegador é compatível com esses recursos:

if (window.PublicKeyCredential && PublicKeyCredential.getClientCapabilities) {
  const capabilities = await PublicKeyCredential.getClientCapabilities();
  // Check if conditional mediation is available.  
  if (capabilities.conditionalGet === true) {
    // The browser supports conditional mediation.
  }
}

Buscar informações do back-end

O back-end precisa fornecer várias opções ao front-end para iniciar a chamada navigator.credentials.get(). Essas opções geralmente são buscadas como um objeto JSON de um endpoint no seu servidor.

As principais propriedades no objeto de opções incluem:

  • challenge: um desafio gerado pelo servidor em um ArrayBuffer (normalmente codificado em Base64URL para transporte JSON). Isso é essencial para evitar ataques de repetição. Seu servidor precisa gerar um novo desafio para cada tentativa de login e invalidá-lo após um curto período ou se uma tentativa falhar.
  • allowCredentials: uma matriz de descritores de credenciais. Transmita uma matriz vazia. Isso solicita ao navegador que liste todas as credenciais do rpId especificado.
  • userVerification: especifica sua preferência de verificação do usuário, como exigir um bloqueio de tela do dispositivo. O valor padrão e recomendado é "preferred". Os valores possíveis são:

    • "required": a verificação do usuário precisa ser feita pelo autenticador (como PIN ou biometria). A operação falha se a verificação não puder ser realizada.
    • "preferred": o autenticador tenta fazer a verificação do usuário, mas a operação pode ser concluída sem ela.
    • "discouraged": o autenticador deve evitar a verificação do usuário, se possível.
  • rpId: o ID da parte confiável, geralmente o domínio do seu site (como example.com). Esse valor precisa corresponder exatamente ao rp.id usado quando a credencial de chave de acesso foi criada.

Seu servidor precisa construir esse objeto de opções. Os valores de ArrayBuffer (como o challenge) precisam ser codificados em Base64URL para transporte JSON. No front-end, depois de analisar o JSON, use PublicKeyCredential.parseRequestOptionsFromJSON() para converter o objeto (incluindo a decodificação de strings Base64URL) no formato esperado por navigator.credentials.get().

O snippet de código a seguir mostra como buscar e decodificar as informações necessárias para autenticar com uma chave de acesso.

// Fetch an encoded PubicKeyCredentialRequestOptions from the server.
const _options = await fetch('/webauthn/signinRequest');

// Deserialize and decode the PublicKeyCredentialRequestOptions.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseRequestOptionsFromJSON(decoded_options);
...

Chame a API WebAuthn com a flag conditional para autenticar o usuário.

Depois de preparar o objeto publicKeyCredentialRequestOptions (chamado de options no código de exemplo abaixo), chame navigator.credentials.get() para iniciar a autenticação condicional com chaves de acesso.

// To abort a WebAuthn call, instantiate an AbortController.
const abortController = new AbortController();

// Invoke WebAuthn to authenticate with a passkey.
const credential = await navigator.credentials.get({
  publicKey: options,
  signal: abortController.signal,
  // Specify 'conditional' to activate conditional UI
  mediation: 'conditional'
});

Parâmetros principais para essa chamada:

  • publicKey: precisa ser o objeto publicKeyCredentialRequestOptions (chamado options no exemplo) que você buscou do servidor e processou na etapa anterior.
  • signal: transmitir um sinal de AbortController (como abortController.signal) permite cancelar de maneira programática a solicitação de get(). Isso é útil quando você quer invocar outra chamada WebAuthn.
  • mediation: 'conditional': essa é a flag crucial que torna a chamada WebAuthn condicional. Ele informa ao navegador para aguardar a interação do usuário com um comando de preenchimento automático em vez de mostrar imediatamente uma caixa de diálogo modal.

Envie a credencial de chave pública retornada ao servidor RP

AIf the user selects a passkey and successfully verifies their identity (for instance, using their device screen lock), the navigator.credentials.get() promise resolves. Isso retorna um objeto PublicKeyCredential para seu front-end.

A promessa pode ser rejeitada por vários motivos. Você precisa processar esses erros no seu código verificando a propriedade name do objeto Error:

  • NotAllowedError: o usuário cancelou a operação ou nenhuma chave de acesso foi selecionada.
  • AbortError: a operação foi cancelada, possivelmente pelo seu código usando um AbortController.
  • Outras exceções: ocorreu um erro inesperado. Normalmente, o navegador mostra uma caixa de diálogo de erro ao usuário.

O objeto PublicKeyCredential contém várias propriedades. As principais propriedades relevantes para autenticação incluem:

  • id: o ID codificado em base64url da credencial de chave de acesso autenticada.
  • rawId: Uma versão ArrayBuffer do ID da credencial.
  • response.clientDataJSON: um ArrayBuffer de dados do cliente. Esse campo contém informações como o desafio e a origem que seu servidor precisa verificar.
  • response.authenticatorData: Um ArrayBuffer de dados do autenticador. Esse campo inclui informações como o ID da RP.
  • response.signature: Um ArrayBuffer que contém a assinatura. Esse valor é o núcleo da credencial, e seu servidor precisa verificar essa assinatura usando a chave pública armazenada para a credencial .
  • response.userHandle: um ArrayBuffer que contém o ID do usuário fornecido durante o registro da chave de acesso.
  • authenticatorAttachment: indica se o autenticador faz parte do dispositivo cliente (platform) ou é externo (cross-platform). Um anexo cross-platform pode ocorrer se o usuário fez login com um smartphone. Nesses casos, peça para eles criar uma chave de acesso no dispositivo atual para facilitar o acesso no futuro.
  • type: este campo está sempre definido como "public-key".

Para enviar esse objeto PublicKeyCredential ao back-end, primeiro chame o método .toJSON(). Esse método cria uma versão serializável em JSON da credencial, que processa corretamente a conversão de propriedades ArrayBuffer (como rawId, clientDataJSON, authenticatorData, signature e userHandle) para strings codificadas em Base64URL. Em seguida, use JSON.stringify() para converter esse objeto em uma string e envie no corpo da solicitação ao servidor.

...
// Encode and serialize the PublicKeyCredential.
const _result = credential.toJSON();
const result = JSON.stringify(_result);

// Encode and send the credential to the server for verification.  
const response = await fetch('/webauthn/signinResponse', {
  method: 'post',
  credentials: 'same-origin',
  body: result
});

Verificar a assinatura

Quando o servidor de back-end recebe a credencial de chave pública, ele precisa verificar a autenticidade dela. Isso envolve:

  1. Analisando os dados de credenciais.
  2. Pesquisando a chave pública armazenada associada ao id da credencial.
  3. Verificando o signature recebido em relação à chave pública armazenada.
  4. Validar outros dados, como o desafio e a origem.

Recomendamos usar uma biblioteca FIDO/WebAuthn do lado do servidor para processar essas operações criptográficas com segurança. Você encontra bibliotecas de código aberto no repositório awesome-webauthn do GitHub (link em inglês).

Se a assinatura e todas as outras declarações forem válidas, o servidor poderá fazer login do usuário. Para etapas detalhadas de validação do lado do servidor, consulte Autenticação de chaves de acesso do lado do servidor

Sinaliza se as credenciais correspondentes não forem encontradas no back-end.

Se o servidor de back-end não encontrar uma credencial com um ID correspondente durante o login, talvez o usuário tenha excluído essa chave de acesso do servidor mas não do provedor dela. Essa incompatibilidade pode levar a uma experiência confusa para o usuário se o provedor de chave de acesso continuar sugerindo uma chave que não funciona mais com seu site. Para melhorar isso, sinalize ao provedor de chaves de acesso para remover a chave de acesso órfã.

É possível usar o método PublicKeyCredential.signalUnknownCredential(), parte da API Webauthn Signal, para informar ao provedor de chaves de acesso que a credencial especificada foi removida ou não existe. Chame esse método estático no lado do cliente se o servidor indicar (por exemplo, com um código de status HTTP específico, como 404) que um ID de credencial apresentado é desconhecido. Forneça o ID do RP e o ID da credencial desconhecida a esse método. O provedor de chaves de acesso, se compatível com o indicador, precisa remover a chave de acesso.

// Detect authentication failure due to lack of the credential
if (response.status === 404) {
  // Feature detection
  if (PublicKeyCredential.signalUnknownCredential) {
    await PublicKeyCredential.signalUnknownCredential({
      rpId: "example.com",
      credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA" // base64url encoded credential ID
    });
  } else {
    // Encourage the user to delete the passkey from the password manager nevertheless.
    ...
  }
}

Após a autenticação

Dependendo de como o usuário fez login, sugerimos fluxos diferentes a seguir.

Se o usuário fez login sem uma chave de acesso

Se o usuário fez login no seu site sem uma chave de acesso, talvez ele não tenha uma chave de acesso registrada para essa conta ou no dispositivo atual. Este é um momento oportuno para incentivar a criação de chaves de acesso. Considere as seguintes abordagens:

  • Fazer upgrade de senhas para chaves de acesso: use a criação condicional, um recurso da WebAuthn que permite ao navegador criar automaticamente uma chave de acesso para o usuário após um login bem-sucedido com senha. Isso pode melhorar significativamente a adoção de chaves de acesso ao simplificar o processo de criação. Saiba como funciona e como implementar em Ajudar os usuários a adotar chaves de acesso com mais facilidade
  • Solicitar manualmente a criação de uma chave de acesso: incentive os usuários a criar uma chave de acesso. Isso pode ser eficaz depois que um usuário conclui um processo de login mais complexo, como a autenticação multifator (MFA). No entanto, evite solicitações excessivas, que podem ser intrusivas para a experiência do usuário".

Para saber como incentivar os usuários a criar uma chave de acesso e conhecer outras boas práticas, consulte os exemplos em Como comunicar chaves de acesso aos usuários.

Se o usuário tiver feito login com uma chave de acesso

Depois que um usuário faz login com uma chave de acesso, você tem várias oportunidades para melhorar ainda mais a experiência e manter a consistência da conta.

Incentivar a criação de uma nova chave de acesso após uma autenticação entre dispositivos

Se um usuário fizer login com uma chave de acesso usando um mecanismo entre dispositivos (por exemplo, lendo um QR code com o smartphone), a chave de acesso usada poderá não ser armazenada localmente no dispositivo em que ele está fazendo login. Isso pode acontecer quando:

  • Eles têm uma chave de acesso, mas em um provedor que não é compatível com o sistema operacional ou navegador de login.
  • O usuário perdeu o acesso ao provedor da chave de acesso no dispositivo de login, mas uma chave de acesso ainda está disponível em outro.

Nesse caso, peça para o usuário criar uma nova chave de acesso no dispositivo atual. Isso pode evitar que eles repitam o processo de login entre dispositivos no futuro. Para determinar se o usuário fez login usando uma chave de acesso entre dispositivos, verifique a propriedade authenticatorAttachment da credencial. Se o valor for "cross-platform", isso indica uma autenticação entre dispositivos. Se for o caso, explique a conveniência de criar uma nova chave de acesso e oriente o processo de criação.

Sincronizar detalhes da chave de acesso com o provedor usando indicadores

Para garantir consistência e uma melhor experiência do usuário, a parte confiável (RP) pode usar a API WebAuthn Signals para comunicar atualizações sobre credenciais e informações do usuário ao provedor de chaves de acesso.

Por exemplo, para manter a lista de chaves de acesso de um usuário precisa, mantenha as credenciais sincronizadas no back-end. Você pode sinalizar que uma chave de acesso não existe mais para que os provedores de chave de acesso possam remover as chaves de acesso desnecessárias.

Da mesma forma, você pode sinalizar se um usuário atualizar o nome de usuário ou o nome de exibição no seu serviço para manter as informações do usuário exibidas pelo provedor de chaves de acesso (por exemplo, em caixas de diálogo de seleção de conta) atualizadas.

Para saber mais sobre as práticas recomendadas para manter as chaves de acesso consistentes, consulte Manter as chaves de acesso consistentes com as credenciais no seu servidor com a API Signal.

Não pedir um segundo fator

As chaves de acesso oferecem proteção robusta e integrada contra ameaças comuns, como phishing. Portanto, um segundo fator de autenticação não adiciona um valor de segurança significativo. Em vez disso, ela cria uma etapa desnecessária para os usuários durante o login.

Lista de verificação

  • Permitir que os usuários façam login com uma chave de acesso usando o preenchimento automático de formulários.
  • Sinaliza quando a credencial correspondente de uma chave de acesso não é encontrada no back-end.
  • Peça aos usuários para criar uma chave de acesso manualmente se eles ainda não tiverem feito isso após um login.
  • Criar automaticamente uma chave de acesso (criação condicional) depois que o usuário fizer login com uma senha (e um segundo fator).
  • Solicite a criação de uma chave de acesso local se o usuário tiver feito login com uma chave de acesso entre dispositivos.
  • Sinalize a lista de chaves de acesso disponíveis e os detalhes atualizados do usuário (nome de usuário, nome de exibição) para o provedor após o login ou quando ocorrerem mudanças.

Recursos