Criar uma chave de acesso para logins sem senha

As chaves de acesso tornam as contas de usuário mais seguras, simples e fáceis de usar.

Eiji Kitamura
Eiji Kitamura

O uso de chaves de acesso em vez de senhas é uma ótima maneira de tornar as contas dos usuários mais seguras, simples, fáceis de usar e sem senha. Com uma chave de acesso, o usuário pode fazer login em um site ou app usando apenas a impressão digital, o rosto ou o PIN do dispositivo.

Uma chave de acesso precisa ser criada, associada a uma conta de usuário e ter a chave pública armazenada no seu servidor antes que o usuário possa fazer login com ela.

Como funciona

Um usuário pode ser solicitado a criar uma chave de acesso em uma das seguintes situações:

  • Quando um usuário faz login usando uma senha.
  • Quando um usuário faz login usando uma chave de acesso de outro dispositivo (ou seja, o authenticatorAttachment é cross-platform).
  • Em uma página dedicada em que os usuários podem gerenciar as chaves de acesso

Para criar uma chave de acesso, use a API WebAuthn.

Os quatro componentes do fluxo de registro da chave de acesso são:

  • Back-end: o servidor de back-end que contém o banco de dados de contas que armazena a chave pública e outros metadados sobre a chave de acesso.
  • Front-end: o front-end que se comunica com o navegador e envia solicitações de busca para o back-end.
  • Navegador: o navegador do usuário que está executando o JavaScript.
  • Autenticador: o autenticador do usuário que cria e armazena a chave de acesso. Isso pode incluir o gerenciador de senhas no mesmo dispositivo que o navegador (por exemplo, ao usar o Windows Hello) ou em outro dispositivo, como um smartphone.
Diagrama de registro da chave de acesso

O processo para adicionar uma nova chave de acesso a uma conta de usuário é o seguinte:

  1. Um usuário faz login no site.
  2. Depois que o usuário faz login, ele solicita a criação de uma chave de acesso no front-end, por exemplo, pressionando um botão "Criar uma chave de acesso".
  3. O front-end solicita informações do back-end para criar uma chave de acesso, como informações do usuário, um desafio e os IDs de credencial a serem excluídos.
  4. O front-end chama navigator.credentials.create() para criar uma chave de acesso. Essa chamada retorna uma promessa.
  5. Uma chave de acesso é criada após o consentimento do usuário usando o bloqueio de tela do dispositivo. A promessa é resolvida e uma credencial de chave pública é retornada para o front-end.
  6. O front-end envia a credencial de chave pública para o back-end e armazena o ID da credencial e a chave pública associada à conta do usuário para futuras autenticações.

Compatibilidades

A maioria dos navegadores oferece suporte ao WebAuthn, mas há pequenas lacunas. Consulte Suporte a dispositivos: chaves de acesso.dev para saber qual combinação de navegadores e sistemas operacionais oferece suporte à criação de chaves de acesso.

Criar uma chave de acesso

Confira como um front-end deve operar em uma solicitação para criar uma nova chave de acesso.

Detecção de recursos

Antes de mostrar o botão "Criar uma chave de acesso", verifique se:

  • O navegador oferece suporte para WebAuthn com PublicKeyCredential.

Compatibilidade com navegadores

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

Origem

  • O dispositivo oferece suporte a um autenticador de plataforma (pode criar uma chave de acesso e autenticar com ela) com PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().

Compatibilidade com navegadores

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

Origem

Compatibilidade com navegadores

  • Chrome: 108.
  • Edge: 108.
  • Firefox: 119
  • Safari: 16.

Origem

// Availability of `window.PublicKeyCredential` means WebAuthn is usable.  
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.  
// `​​isConditionalMediationAvailable` means the feature detection is usable.  
if (window.PublicKeyCredential &&  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&  
    PublicKeyCredential.​​isConditionalMediationAvailable) {  
  // Check if user verifying platform authenticator is available.  
  Promise.all([  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),  
    PublicKeyCredential.​​isConditionalMediationAvailable(),  
  ]).then(results => {  
    if (results.every(r => r === true)) {  
      // Display "Create a new passkey" button  
    }  
  });  
}  

Até que todas as condições sejam atendidas, as chaves de acesso não terão suporte neste navegador. O botão "Criar uma nova chave de acesso" não vai aparecer até esse momento.

Buscar informações importantes do back-end

Quando o usuário clica no botão, extraia informações importantes para chamar navigator.credentials.create() do back-end:

  • challenge: um desafio gerado pelo servidor em ArrayBuffer para este registro. Isso é obrigatório, mas não utilizado durante o registro, a menos que seja um atestado, um tópico avançado não abordado aqui.
  • user.id: o ID exclusivo de um usuário. Esse valor precisa ser um ArrayBuffer que não inclua informações de identificação pessoal, por exemplo, endereços de e-mail ou nomes de usuário. Um valor aleatório de 16 bytes gerado por conta funciona bem.
  • user.name: esse campo precisa conter um identificador exclusivo da conta que o usuário vai reconhecer, como o endereço de e-mail ou o nome de usuário. Ele vai ser exibido no seletor de contas. Se você utiliza um nome de usuário, use o mesmo valor da autenticação por senha.
  • user.displayName: este campo é um nome obrigatório e mais fácil de usar para a conta. Ele não precisa ser exclusivo e pode ser o nome escolhido pelo usuário. Se o site não tiver um valor adequado para incluir aqui, transmita uma string vazia. Essa informação pode ser exibida no seletor de contas dependendo do navegador.
  • excludeCredentials: impede o registro do mesmo dispositivo fornecendo uma lista de IDs de credenciais já registrados. O membro transports, se fornecido, precisa conter o resultado da chamada de getTransports() durante o registro de cada credencial.

Chamar a API WebAuthn para criar uma chave de acesso

Chame navigator.credentials.create() para criar uma nova chave de acesso. A API retorna uma promessa, aguardando a interação do usuário exibindo uma caixa de diálogo modal.

Compatibilidade com navegadores

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

Origem

const publicKeyCredentialCreationOptions = {
  challenge: *****,
  rp: {
    name: "Example",
    id: "example.com",
  },
  user: {
    id: *****,
    name: "john78",
    displayName: "John",
  },
  pubKeyCredParams: [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}],
  excludeCredentials: [{
    id: *****,
    type: 'public-key',
    transports: ['internal'],
  }],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    requireResidentKey: true,
  }
};

const credential = await navigator.credentials.create({
  publicKey: publicKeyCredentialCreationOptions
});

// Encode and send the credential to the server for verification.  

Os parâmetros não explicados acima são:

  • rp.id: um ID de RP é um domínio, e um site pode especificar o próprio domínio ou um sufixo registrável. Por exemplo, se a origem de uma RP for https://login.example.com:1337, o ID da RP poderá ser login.example.com ou example.com. Se o ID da RP for especificado como example.com, o usuário poderá fazer a autenticação em login.example.com ou em qualquer subdomínio em example.com.

  • rp.name: o nome do RP.

  • pubKeyCredParams: esse campo especifica os algoritmos de chave pública compatíveis com a RP. Recomendamos defini-lo como [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]. Isso especifica a compatibilidade de ECDSA com P-256 e RSA PKCS#1, além de oferecer cobertura completa.

  • authenticatorSelection.authenticatorAttachment: defina como "platform" se a criação da chave de acesso for um upgrade de uma senha, por exemplo, em uma promoção após um login. "platform" indica que o RP quer um autenticador de plataforma (um autenticador incorporado ao dispositivo da plataforma) que não solicitará a inserção, por exemplo, de uma chave de segurança USB. O usuário tem uma opção mais simples para criar uma chave de acesso.

  • authenticatorSelection.requireResidentKey: defina como um booleano "true". Uma credencial detectável (chave residente) armazena informações do usuário na chave de acesso e permite que os usuários selecionem a conta após a autenticação. Saiba mais sobre as credenciais detectáveis em Detalhamento das credenciais detectáveis.

  • authenticatorSelection.userVerification: indica se a verificação de um usuário usando o bloqueio de tela do dispositivo é "required", "preferred" ou "discouraged". O padrão é "preferred", o que significa que o autenticador pode pular a verificação do usuário. Defina como "preferred" ou omita a propriedade.

Enviar a credencial de chave pública retornada para o back-end

Depois que o usuário consente usando o bloqueio de tela do dispositivo, uma chave de acesso é criada e a promessa é resolvida, retornando um objeto PublicKeyCredential para o front-end.

A promessa pode ser rejeitada por diferentes motivos. É possível processar esses erros verificando a propriedade name do objeto Error:

  • InvalidStateError: uma chave de acesso já existe no dispositivo. Nenhuma caixa de diálogo de erro será mostrada ao usuário, e o site não deve tratar isso como um erro. O usuário queria que o dispositivo local fosse registrado, e ele foi.
  • NotAllowedError: o usuário cancelou a operação.
  • Outras exceções: algo inesperado aconteceu. O navegador mostra uma caixa de diálogo de erro para o usuário.

O objeto de credencial de chave pública contém as seguintes propriedades:

  • id: um ID codificado em Base64URL da chave de acesso criada. Esse ID ajuda o navegador a determinar se uma chave de acesso correspondente está no dispositivo após a autenticação. Esse valor precisa ser armazenado no banco de dados no back-end.
  • rawId: uma versão ArrayBuffer do ID da credencial.
  • response.clientDataJSON: dados do cliente codificados em ArrayBuffer.
  • response.attestationObject: um objeto de atestado codificado em ArrayBuffer. Isso contém informações importantes, como um ID da RP, sinalizações e uma chave pública.
  • authenticatorAttachment: retorna "platform" quando a credencial é criada em um dispositivo com suporte à chave de acesso.
  • type: esse campo é sempre definido como "public-key".

Se você usar uma biblioteca para processar o objeto de credencial de chave pública no back-end, recomendamos enviar o objeto inteiro para o back-end depois de codificá-lo parcialmente com base64url.

Salvar a credencial

Ao receber a credencial de chave pública no back-end, transmita-a para a biblioteca FIDO processar o objeto.

Em seguida, é possível armazenar as informações extraídas da credencial no banco de dados para uso futuro. A lista a seguir inclui algumas propriedades típicas para salvar:

  • ID da credencial (chave primária)
  • ID do usuário
  • Chave pública

A credencial de chave pública também inclui as seguintes informações que você pode salvar no banco de dados:

Siga as instruções mais detalhadas em Registro de chave de acesso no servidor.

Para autenticar o usuário, leia Fazer login com uma chave de acesso usando o preenchimento automático de formulários.

Recursos