Implementar chaves de acesso com o preenchimento automático de formulário em um app da Web

1. Antes de começar

O uso de chaves de acesso em vez de senhas é uma ótima maneira de tornar as contas dos usuários mais seguras, simples e fáceis de usar. Com uma chave de acesso, o usuário pode fazer login em um site ou app usando o recurso de bloqueio de tela, como impressão digital, rosto ou PIN do dispositivo. Uma chave de acesso precisa ser criada, associada a uma conta de usuário e ter a chave pública armazenada em um servidor, antes que o usuário possa fazer login com ela.

Neste codelab, você aprende a transformar um login básico de nome de usuário e senha em um login com suporte a chaves de acesso e que inclua o seguinte:

  • Um botão que cria uma chave de acesso depois que o usuário faz login.
  • Uma IU que exibe uma lista de chaves de acesso registradas.
  • O formulário de login atual, que permite que os usuários se conectem usando uma chave de acesso registrada pelo preenchimento automático de formulários.

Pré-requisitos

O que você vai aprender

  • Como criar uma chave de acesso.
  • Como autenticar usuários com uma chave de acesso.
  • Como permitir que um formulário sugira uma chave de acesso como opção de login.

O que é necessário

Uma das seguintes combinações de dispositivos:

  • Google Chrome em um dispositivo com o Android 9 ou mais recente, de preferência com um sensor biométrico.
  • Chrome com um dispositivo Windows 10 ou mais recente.
  • Safari 16 ou mais recente em um iPhone com iOS 16 ou mais recente ou um iPad com o iPadOS 16 ou mais recente.
  • Safari 16 ou versão mais recente ou Chrome com um dispositivo desktop Apple que execute o macOS Ventura ou mais recente.

2. Começar a configuração

Neste codelab, você usa um serviço chamado Glitch, que permite editar o código do lado do cliente e do servidor com JavaScript e implantá-lo somente pelo navegador.

Abrir o projeto

  1. Abra o projeto no Glitch.
  2. Clique em Remix para bifurcar o projeto Glitch.
  3. No menu de navegação na parte de baixo do Glitch, clique em Visualização > Visualizar em uma nova janela. Outra guia será aberta no navegador.

Botão "Visualizar em uma nova janela" no menu de navegação na parte de baixo do Glitch

Examinar o estado inicial do site

  1. Na guia de visualização, insira um nome de usuário aleatório e clique em Próxima.
  2. Digite uma senha aleatória e clique em Fazer login. A senha será ignorada, mas a autenticação ainda será feita e você vai acessar a página inicial.
  3. Se quiser, mude seu nome de exibição. Isso é tudo o que você pode fazer no estado inicial.
  4. Clique em Sair.

Nesse estado, os usuários precisam inserir uma senha sempre que fizerem login. É possível adicionar suporte à chave de acesso neste formulário para que os usuários possam fazer login com a funcionalidade de bloqueio de tela do dispositivo. Teste o estado final em https://passkeys-codelab.glitch.me/.

Para mais informações sobre como as chaves de acesso funcionam, consulte Como as chaves de acesso funcionam?.

3. Adicionar a capacidade de criar uma chave de acesso

Para permitir que os usuários façam a autenticação com uma chave de acesso, você precisa permitir que eles criem e registrem uma chave de acesso e a armazenem no servidor.

Uma caixa de diálogo para verificação do usuário da chave de acesso vai aparecer quando ela for criada.

Recomendamos permitir a criação de uma chave de acesso depois que o usuário fizer login com uma senha e adicionar uma IU que permita criar uma chave de acesso e consultar uma lista de todas as chaves de acesso registradas na página /home. Na próxima seção, é possível ativar uma função que cria e registra uma chave de acesso.

Criar a função registerCredential()

  1. No Glitch, navegue até o arquivo public/client.js e role até o fim.
  2. Após o comentário relevante, adicione a seguinte função registerCredential():

public/client. js

// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {

  // TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.

  // TODO: Add an ability to create a passkey: Create a credential.

  // TODO: Add an ability to create a passkey: Register the credential to the server endpoint.

};

Essa função cria e registra uma chave de acesso no servidor.

Acessar o desafio e outras opções do endpoint do servidor

Antes de criar uma chave de acesso, você precisa solicitar parâmetros para transmitir a WebAuthn do servidor, incluindo um desafio. A WebAuthn é uma API de navegador que permite criar uma chave de acesso e autenticar o usuário com ela. Felizmente, você já tem um endpoint de servidor que responde com esses parâmetros neste codelab.

  • Para acessar o desafio e outras opções do endpoint do servidor, adicione o seguinte código no corpo da função registerCredential() após o comentário relevante:

public/client.js

// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/registerRequest');

O snippet de código a seguir inclui exemplos de opções recebidas do servidor:

{
  challenge: *****,
  rp: {
    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', 'hybrid'],
  }],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    requireResidentKey: true,
  }
}

O protocolo entre um servidor e um cliente não faz parte da especificação WebAuthn. No entanto, o servidor deste codelab foi projetado para retornar um JSON o mais semelhante possível ao dicionário PublicKeyCredentialCreationOptions transmitido para a API WebAuthn navigator.credentials.create().

A tabela a seguir não é completa, mas contém os parâmetros importantes no dicionário PublicKeyCredentialCreationOptions:

Parâmetros

Descrições

challenge

Um desafio gerado pelo servidor em um objeto ArrayBuffer para este registro. Isso é necessário, mas não utilizado durante o registro, a menos que seja um atestado, um tema avançado que não é abordado neste codelab.

user.id

ID exclusivo de um usuário. Esse valor precisa ser um objeto ArrayBuffer que não inclua informações de identidade pessoal, como 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 pode ser reconhecido pelo usuário, como o endereço de e-mail ou o nome de usuário. Ele aparece no seletor de contas. Se você utiliza um nome de usuário, use o mesmo valor da autenticação por senha.

user.displayName

Esse campo é um nome opcional e 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.

rp.id

Um ID de parte confiável (RP, na sigla em inglês) é um domínio. Um site pode especificar o próprio domínio ou um sufixo registrável. Por exemplo, se a origem de uma RP é https://login.example.com:1337, o ID da RP pode 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 outros subdomínios de example.com.

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.

excludeCredentials

Mostra uma lista de IDs de credencial já registrados para evitar que o mesmo dispositivo seja registrado duas vezes. Se fornecido, o membro transports precisa conter o resultado da chamada da função getTransports() durante o registro de cada credencial.

authenticatorSelection.authenticatorAttachment

Defina como um valor "platform". Isso indica que você quer um autenticador incorporado no dispositivo da plataforma para que o usuário não precise inserir algo como uma chave de segurança USB.

authenticatorSelection.requireResidentKey

Defina como um valor booleano true. Uma credencial detectável (chave residente) pode ser usada sem que o servidor tenha que fornecer o ID da credencial. Portanto, ela é compatível com o preenchimento automático.

authenticatorSelection.userVerification

Defina-o como um valor de "preferred" ou omita a informação porque ele é o valor padrão. Indica se a verificação de um usuário que usa o bloqueio de tela do dispositivo é "required", "preferred" ou "discouraged". A definição como um valor "preferred" solicita a verificação do usuário, quando o dispositivo for compatível.

Criar uma credencial

  1. No corpo da função registerCredential(), após o comentário relevante, converta alguns parâmetros codificados com Base64URL de volta ao binário, especificamente as strings user.id e challenge, e instâncias da string id incluídas na matriz excludeCredentials:

public/client.js

// TODO: Add an ability to create a passkey: Create a credential.
// Base64URL decode some values.
options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);

if (options.excludeCredentials) {
  for (let cred of options.excludeCredentials) {
    cred.id = base64url.decode(cred.id);
  }
}
  1. Na próxima linha, defina authenticatorSelection.authenticatorAttachment como "platform" e authenticatorSelection.requireResidentKey como true. Isso permite o uso de um autenticador de plataforma (o próprio dispositivo) com um recurso de credencial detectável.

public/client.js

// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
  authenticatorAttachment: 'platform',
  requireResidentKey: true
}
  1. Na próxima linha, chame o método navigator.credentials.create() para criar uma credencial.

public/client.js

// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
  publicKey: options,
});

Nessa chamada, o navegador tenta verificar a identidade do usuário usando o bloqueio de tela do dispositivo.

Registrar a credencial no endpoint do servidor

Depois que o usuário verifica a própria identidade, uma chave de acesso é criada e armazenada. O site recebe um objeto de credencial que contém uma chave pública e que pode ser enviada ao servidor para registrar a chave de acesso.

O snippet de código abaixo contém um exemplo de objeto de credencial:

{
  "id": *****,
  "rawId": *****,
  "type": "public-key",
  "response": {
    "clientDataJSON": *****,
    "attestationObject": *****,
    "transports": ["internal", "hybrid"]
  },
  "authenticatorAttachment": "platform"
}

A tabela a seguir não é completa, mas contém os parâmetros importantes no objeto PublicKeyCredential:

Parâmetros

Descrições

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 de objeto ArrayBuffer do ID da credencial.

response.clientDataJSON

Um objeto ArrayBuffer codificado com dados do cliente.

response.attestationObject

Um objeto de atestado codificado por ArrayBuffer. Ele contém informações importantes, como um ID da RP, sinalizações e uma chave pública.

response.transports

Uma lista de transportes compatíveis com o dispositivo: "internal" significa que ele oferece suporte a uma chave de acesso. "hybrid" significa que ele também oferece suporte à autenticação em outro dispositivo.

authenticatorAttachment

Retorna "platform" quando a credencial é criada em um dispositivo com suporte à chave de acesso.

Para enviar o objeto da credencial para o servidor, siga estas etapas:

  1. Codifique os parâmetros binários da credencial como Base64URL para que ela possa ser entregue ao servidor como uma string:

public/client.js

// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;

// The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
if (cred.authenticatorAttachment) {
  credential.authenticatorAttachment = cred.authenticatorAttachment;
}

// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const attestationObject = base64url.encode(cred.response.attestationObject);

// Obtain transports.
const transports = cred.response.getTransports ? cred.response.getTransports() : [];

credential.response = {
  clientDataJSON,
  attestationObject,
  transports
};
  1. Na próxima linha, envie o objeto para o servidor:

public/client.js

return await _fetch('/auth/registerResponse', credential);

Quando você executa o programa, o servidor retorna HTTP code 200, que indica que a credencial é registrada.

Agora você tem a função registerCredential() completa.

Revisar o código da solução para esta seção

public/client.js

// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {

  // TODO: Add an ability to create a passkey: Obtain the challenge and other options from server endpoint.
  const options = await _fetch('/auth/registerRequest');

  // TODO: Add an ability to create a passkey: Create a credential.
  // Base64URL decode some values.

  options.user.id = base64url.decode(options.user.id);
  options.challenge = base64url.decode(options.challenge);

  if (options.excludeCredentials) {
    for (let cred of options.excludeCredentials) {
      cred.id = base64url.decode(cred.id);
    }
  }

  // Use platform authenticator and discoverable credential.
  options.authenticatorSelection = {
    authenticatorAttachment: 'platform',
    requireResidentKey: true
  }

  // Invoke the WebAuthn create() method.
  const cred = await navigator.credentials.create({
    publicKey: options,
  });

  // TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;

  // The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
  if (cred.authenticatorAttachment) {
    credential.authenticatorAttachment = cred.authenticatorAttachment;
  }

  // Base64URL encode some values.
  const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
  const attestationObject =
  base64url.encode(cred.response.attestationObject);

  // Obtain transports.
  const transports = cred.response.getTransports ?
  cred.response.getTransports() : [];

  credential.response = {
    clientDataJSON,
    attestationObject,
    transports
  };

  return await _fetch('/auth/registerResponse', credential);
};

4. Criar uma IU para registrar e gerenciar credenciais da chave de acesso

Agora que a função registerCredential() está disponível, você precisa de um botão para invocá-la. Além disso, é necessário exibir uma lista de chaves de acesso registradas.

Chaves de acesso registradas listadas na /página inicial

Adicionar marcador de posição HTML

  1. No Glitch, navegue até o arquivo views/home.html.
  2. Depois do comentário relevante, adicione um marcador de posição da IU que mostre um botão para registrar uma chave de acesso e uma lista de chaves de acesso:

views/home.html

​​<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
  <h3 class="mdc-typography mdc-typography--headline6"> Your registered
  passkeys:</h3>
  <div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button>

O elemento div#list é o marcador da lista.

Verificar o suporte a chaves de acesso

Para mostrar apenas a opção de criar uma chave de acesso para usuários com dispositivos que oferecem suporte a chaves de acesso, primeiro você precisa verificar se a WebAuthn está disponível. Em caso afirmativo, será necessário remover a classe hidden para mostrar o botão Criar uma chave de acesso.

Para verificar se um ambiente oferece suporte a chaves de acesso, siga estas etapas:

  1. No final do arquivo views/home.html, após o comentário relevante, escreva uma condição que será executada se window.PublicKeyCredential, PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable e PublicKeyCredential.isConditionalMediationAvailable forem true.

views/home.html

// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Feature detections
if (window.PublicKeyCredential &&
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
    PublicKeyCredential.isConditionalMediationAvailable) {
  1. No corpo da condição, verifique se o dispositivo pode criar uma chave de acesso e se ela pode ser sugerida em um preenchimento automático de formulário.

views/home.html

try {
  const results = await Promise.all([

    // Is platform authenticator available in this browser?
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),

    // Is conditional UI available in this browser?
    PublicKeyCredential.isConditionalMediationAvailable()
  ]);
  1. Se todas as condições forem atendidas, mostre o botão para criar uma chave de acesso. Caso contrário, mostre uma mensagem de aviso.

views/home.html

    if (results.every(r => r === true)) {

      // If conditional UI is available, reveal the Create a passkey button.
      createPasskey.classList.remove('hidden');
    } else {

      // If conditional UI isn't available, show a message.
      $('#message').innerText = 'This device does not support passkeys.';
    }
  } catch (e) {
    console.error(e);
  }
} else {

  // If WebAuthn isn't available, show a message.
  $('#message').innerText = 'This device does not support passkeys.';
}

Renderizar chaves de acesso registradas em uma lista

  1. Defina uma função renderCredentials() que busque as chaves de acesso registradas no servidor e as renderize em uma lista. Felizmente, você já tem o endpoint do servidor /auth/getKeys para buscar as chaves de acesso registradas do usuário conectado.

views/home.html

// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
  const res = await _fetch('/auth/getKeys');
  const list = $('#list');
  const creds = html`${res.length > 0 ? html`
    <mwc-list>
      ${res.map(cred => html`
        <mwc-list-item>
          <div class="list-item">
            <div class="entity-name">
              <span>${cred.name || 'Unnamed' }</span>
          </div>
          <div class="buttons">
            <mwc-icon-button data-cred-id="${cred.id}"
            data-name="${cred.name || 'Unnamed' }" @click="${rename}"
            icon="edit"></mwc-icon-button>
            <mwc-icon-button data-cred-id="${cred.id}" @click="${remove}"
            icon="delete"></mwc-icon-button>
          </div>
         </div>
      </mwc-list-item>`)}
  </mwc-list>` : html`
  <mwc-list>
    <mwc-list-item>No credentials found.</mwc-list-item>
  </mwc-list>`}`;
  render(creds, list);
};
  1. Na próxima linha, invoque a função renderCredentials() para exibir chaves de acesso registradas, assim que o usuário acessar a página /home como uma inicialização.

views/home.html

renderCredentials();

Criar e registrar uma chave de acesso

Para criar e registrar uma chave de acesso, você precisa chamar a função registerCredential() implementada anteriormente.

Para acionar a função registerCredential() ao clicar no botão Criar uma chave de acesso, siga estas etapas:

  1. No arquivo após o HTML do marcador, localize a seguinte instrução import:

views/home.html

import {
  $,
  _fetch,
  loading,
  updateCredential,
  unregisterCredential,
} from '/client.js';
  1. No final do corpo da instrução import, adicione a função registerCredential().

views/home.html

// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
  $,
  _fetch,
  loading,
  updateCredential,
  unregisterCredential,
  registerCredential
} from '/client.js';
  1. No final do arquivo, após o comentário relevante, defina uma função register() que invoque a função registerCredential() e uma IU de carregamento, além de chamar renderCredentials() depois de um registro. Isso deixa claro que o navegador cria uma chave de acesso e mostra uma mensagem de erro quando algo dá errado.

views/home.html

// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
  try {

    // Start the loading UI.
    loading.start();

    // Start creating a passkey.
    await registerCredential();

    // Stop the loading UI.
    loading.stop();

    // Render the updated passkey list.
    renderCredentials();
  1. No corpo da função register(), capture exceções. O método navigator.credentials.create() gera um erro InvalidStateError quando uma chave de acesso já existe no dispositivo. Isso é examinado com a matriz excludeCredentials. Nesse caso, mostre uma mensagem relevante para o usuário. Ele também gera um erro NotAllowedError quando o usuário cancela a caixa de diálogo de autenticação. Nesse caso, você faz isso de forma silenciosa.

views/home.html

  } catch (e) {

    // Stop the loading UI.
    loading.stop();

    // An InvalidStateError indicates that a passkey already exists on the device.
    if (e.name === 'InvalidStateError') {
      alert('A passkey already exists for this device.');

    // A NotAllowedError indicates that the user canceled the operation.
    } else if (e.name === 'NotAllowedError') {
      Return;

    // Show other errors in an alert.
    } else {
      alert(e.message);
      console.error(e);
    }
  }
};
  1. Na linha após a função register(), anexe a função register() a um evento click para o botão Criar uma chave de acesso.

views/home.html

createPasskey.addEventListener('click', register);

Revisar o código da solução para esta seção

views/home.html

​​<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
  <h3 class="mdc-typography mdc-typography--headline6"> Your registered
  passkeys:</h3>
  <div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button>

views/home.html

// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
  $,
  _fetch,
  loading,
  updateCredential,
  unregisterCredential,
  registerCredential
} from '/client.js';

views/home.html

// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');

// Feature detections
if (window.PublicKeyCredential &&
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
    PublicKeyCredential.isConditionalMediationAvailable) {
  try {
    const results = await Promise.all([

      // Is platform authenticator available in this browser?
      PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),

      // Is conditional UI available in this browser?
      PublicKeyCredential.isConditionalMediationAvailable()
    ]);
    if (results.every(r => r === true)) {

      // If conditional UI is available, reveal the Create a passkey button.
      createPasskey.classList.remove('hidden');
    } else {

      // If conditional UI isn't available, show a message.
      $('#message').innerText = 'This device does not support passkeys.';
    }
  } catch (e) {
    console.error(e);
  }
} else {

  // If WebAuthn isn't available, show a message.
  $('#message').innerText = 'This device does not support passkeys.';
}

// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
  const res = await _fetch('/auth/getKeys');
  const list = $('#list');
  const creds = html`${res.length > 0 ? html`
  <mwc-list>
    ${res.map(cred => html`
      <mwc-list-item>
        <div class="list-item">
          <div class="entity-name">
            <span>${cred.name || 'Unnamed' }</span>
          </div>
          <div class="buttons">
            <mwc-icon-button data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed' }" @click="${rename}" icon="edit"></mwc-icon-button>
            <mwc-icon-button data-cred-id="${cred.id}" @click="${remove}" icon="delete"></mwc-icon-button>
          </div>
        </div>
      </mwc-list-item>`)}
  </mwc-list>` : html`
  <mwc-list>
    <mwc-list-item>No credentials found.</mwc-list-item>
  </mwc-list>`}`;
  render(creds, list);
};

renderCredentials();

// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
  try {

    // Start the loading UI.
    loading.start();

    // Start creating a passkey.
    await registerCredential();

    // Stop the loading UI.
    loading.stop();

    // Render the updated passkey list.
    renderCredentials();
  } catch (e) {

    // Stop the loading UI.
    loading.stop();

    // An InvalidStateError indicates that a passkey already exists on the device.
    if (e.name === 'InvalidStateError') {
      alert('A passkey already exists for this device.');

    // A NotAllowedError indicates that the user canceled the operation.
    } else if (e.name === 'NotAllowedError') {
      Return;

    // Show other errors in an alert.
    } else {
      alert(e.message);
      console.error(e);
    }
  }
};

createPasskey.addEventListener('click', register);

Faça um teste

Se você seguiu todas as etapas até agora, implementou a capacidade de criar, registrar e exibir chaves de acesso no site.

Para testar, siga estas etapas:

  1. Na guia de visualização, faça login com um nome de usuário e uma senha aleatórios.
  2. Clique em Criar uma chave de acesso.
  3. Verifique sua identidade com o bloqueio de tela do dispositivo.
  4. Confirme se uma chave de acesso está registrada e é exibida na seção Suas chaves de acesso registradas da página da Web.

Chaves de acesso registradas listadas na página /página inicial.

Renomear e remover chaves de acesso registradas

É possível renomear ou excluir as chaves de acesso registradas na lista. Você pode conferir como ela funciona no código, conforme fornecido no codelab.

No Chrome, você pode remover as chaves de acesso registradas em chrome://settings/passkeys no computador ou no gerenciador de senhas nas configurações do Android.

Para saber como renomear e remover chaves de acesso registradas em outras plataformas, consulte as respectivas páginas de suporte para essas plataformas.

5. Adicionar a capacidade de autenticação com uma chave de acesso

Agora os usuários podem criar e registrar uma chave de acesso, e ela está pronta para ser usada como forma de autenticação no seu site com segurança. Agora você precisa adicionar um recurso de autenticação de senha ao seu site.

Criar a função authenticate()

  • No arquivo public/client.js, após o comentário relevante, crie uma função chamada authenticate() que verifica localmente o usuário e, em seguida, no servidor:

public/client.js

// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {

  // TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.

  // TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.

  // TODO: Add an ability to authenticate with a passkey: Verify the credential.

};

Acessar o desafio e outras opções do endpoint do servidor

Antes de pedir ao usuário para se autenticar, você precisa solicitar parâmetros para transmitir a WebAuthn do servidor, incluindo um desafio.

  • No corpo da função authenticate(), após o comentário relevante, chame a função _fetch() para enviar uma solicitação POST ao servidor:

public/client.js

// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/signinRequest');

O servidor deste codelab foi projetado para retornar um JSON o mais semelhante possível ao dicionário PublicKeyCredentialRequestOptions transmitido para a API WebAuthn navigator.credentials.get(). O snippet de código a seguir inclui exemplos de opções disponíveis:

{
  "challenge": *****,
  "rpId": "passkeys-codelab.glitch.me",
  "allowCredentials": []
}

A tabela a seguir não é completa, mas contém os parâmetros importantes no dicionário PublicKeyCredentialRequestOptions:

Parâmetros

Descrições

challenge

Um desafio gerado pelo servidor em um objeto ArrayBuffer. Isso é necessário para evitar ataques repetidos. Nunca aceite o mesmo desafio em uma resposta duas vezes. Considere-o um token CSRF.

rpId

Um ID da RP é um domínio. Um site pode especificar o próprio domínio ou um sufixo registrável. Esse valor precisa corresponder ao parâmetro rp.id usado quando a chave de acesso foi criada.

allowCredentials

Esta propriedade é usada para encontrar autenticadores qualificados para essa autenticação. Passe uma matriz vazia ou deixe-a não especificada para permitir que o navegador mostre um seletor de conta.

userVerification

Defina-o como um valor de "preferred" ou omita a informação porque ele é o valor padrão. Indica se a verificação de um usuário usando o bloqueio de tela do dispositivo é "required", "preferred" ou "discouraged". A definição como um valor "preferred" solicita a verificação do usuário, quando o dispositivo for compatível.

Verificar o usuário localmente e receber uma credencial

  1. No corpo da função authenticate() após o comentário relevante, converta o parâmetro challenge de volta para binário:

public/client.js

// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.
// Base64URL decode the challenge.
options.challenge = base64url.decode(options.challenge);
  1. Transmita uma matriz vazia ao parâmetro allowCredentials para abrir um seletor de contas, quando um usuário for autenticado:

public/client.js

// An empty allowCredentials array invokes an account selector by discoverable credentials.
options.allowCredentials = [];

O seletor de conta usa as informações do usuário armazenadas com a chave de acesso.

  1. Chame o método navigator.credentials.get() com uma opção mediation: 'conditional':

public/client.js

// Invoke the WebAuthn get() method.
const cred = await navigator.credentials.get({
  publicKey: options,

  // Request a conditional UI.
  mediation: 'conditional'
});

Essa opção instrui o navegador a sugerir chaves de acesso condicionalmente como parte do preenchimento automático de formulários.

Verificar a credencial

Depois que o usuário verificar a identidade localmente, você receberá um objeto de credencial contendo uma assinatura que pode ser verificada no servidor.

O snippet de código a seguir inclui um objeto PublicKeyCredential de exemplo:

{
  "id": *****,
  "rawId": *****,
  "type": "public-key",
  "response": {
    "clientDataJSON": *****,
    "authenticatorData": *****,
    "signature": *****,
    "userHandle": *****
  },
  authenticatorAttachment: "platform"
}

A tabela a seguir não é completa, mas contém os parâmetros importantes no objeto PublicKeyCredential:

Parâmetros

Descrições

id

O ID codificado em Base64URL da credencial da chave de acesso autenticada.

rawId

Uma versão de objeto ArrayBuffer do ID da credencial.

response.clientDataJSON

Um objeto ArrayBuffer de dados do cliente. Esse campo contém informações, como o desafio e a origem que o servidor da RP precisa verificar.

response.authenticatorData

Um objeto ArrayBuffer de dados do autenticador. Esse campo contém informações como o ID da RP.

response.signature

Um objeto ArrayBuffer da assinatura. Esse valor é o núcleo da credencial e precisa ser verificado no servidor.

response.userHandle

Um objeto ArrayBuffer que contém o ID do usuário definido no momento da criação. Use esse valor em vez do ID da credencial se o servidor precisar escolher os valores do ID usado ou se o back-end quiser evitar a criação de um índice nos IDs das credenciais.

authenticatorAttachment

Retorna uma string "platform" quando esta credencial vem do dispositivo local. Caso contrário, retorna uma string "cross-platform", especialmente quando o usuário usa um telefone para fazer login. Se o usuário tiver que usar um smartphone para fazer login, peça que ele crie uma chave de acesso no dispositivo local.

Para enviar o objeto da credencial para o servidor, siga estas etapas:

  1. No corpo da função authenticate(), após o comentário relevante, codifique os parâmetros dos binários da credencial para que ela possa ser entregue ao servidor como uma string:

public/client.js

// TODO: Add an ability to authenticate with a passkey: Verify the credential.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;

// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const authenticatorData = base64url.encode(cred.response.authenticatorData);
const signature = base64url.encode(cred.response.signature);
const userHandle = base64url.encode(cred.response.userHandle);

credential.response = {
  clientDataJSON,
  authenticatorData,
  signature,
  userHandle,
};
  1. Envie o objeto para o servidor:

public/client.js

return await _fetch(`/auth/signinResponse`, credential);

Quando você executa o programa, o servidor retorna HTTP code 200, que indica que a credencial foi verificada.

Agora você tem a função authentication() completa.

Revisar o código da solução para esta seção

public/client.js

// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {

  // TODO: Add an ability to authenticate with a passkey: Obtain the
  challenge and other options from the server endpoint.
  const options = await _fetch('/auth/signinRequest');

  // TODO: Add an ability to authenticate with a passkey: Locally verify
  the user and get a credential.
  // Base64URL decode the challenge.
  options.challenge = base64url.decode(options.challenge);

  // The empty allowCredentials array invokes an account selector
  by discoverable credentials.
  options.allowCredentials = [];

  // Invoke the WebAuthn get() function.
  const cred = await navigator.credentials.get({
    publicKey: options,

    // Request a conditional UI.
    mediation: 'conditional'
  });

  // TODO: Add an ability to authenticate with a passkey: Verify the credential.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;

  // Base64URL encode some values.
  const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
  const authenticatorData =
  base64url.encode(cred.response.authenticatorData);
  const signature = base64url.encode(cred.response.signature);
  const userHandle = base64url.encode(cred.response.userHandle);

  credential.response = {
    clientDataJSON,
    authenticatorData,
    signature,
    userHandle,
  };

  return await _fetch(`/auth/signinResponse`, credential);
};

6. Adicionar chaves de acesso ao preenchimento automático do navegador

Quando o usuário retorna, você quer que ele faça login da maneira mais fácil e segura possível. Se você adicionar um botão Fazer login com uma chave de acesso na página de login, o usuário poderá pressionar o botão, selecionar uma chave de acesso no seletor de conta do navegador e usar o bloqueio de tela para verificar a identidade.

No entanto, a transição de uma senha para uma chave de acesso não ocorre para todos os usuários de uma vez só. Isso significa que não é possível se livrar de senhas até que todos os usuários façam a transição para as chaves de acesso. Portanto, deixe o formulário de login baseado em senhas ativo até lá. No entanto, se você deixar um formulário de senha e um botão de chave de acesso, os usuários terão que escolher entre qual deles usar para fazer login. O ideal é um processo de login simples.

É aqui que entra uma IU condicional. Uma IU condicional é um recurso da WebAuthn em que é possível criar um campo de entrada de formulário para sugerir uma chave de acesso como parte dos itens de preenchimento automático, além das senhas. Se um usuário tocar em uma chave de acesso nas sugestões de preenchimento automático, ele vai precisar usar o bloqueio de tela do dispositivo para verificar a identidade localmente. Esta é uma experiência de usuário perfeita, porque a ação do usuário é quase idêntica à de um login baseado em senhas.

Uma chave de acesso sugerida como parte do preenchimento automático de formulários.

Ativar uma IU condicional

Para ativar uma IU condicional, basta adicionar um token webauthn no atributo autocomplete de um campo de entrada. Com o token definido, é possível chamar o método navigator.credentials.get() com a string mediation: 'conditional' para acionar condicionalmente a IU de bloqueio de tela.

  • Para ativar uma IU condicional, substitua os campos de entrada do nome de usuário atuais por este HTML, após o comentário relevante no arquivo view/index.html:

view/index.html

<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
  type="text"
  id="username"
  class="mdc-text-field__input"
  aria-labelledby="username-label"
  name="username"
  autocomplete="username webauthn"
  autofocus />

Detectar recursos, invocar a WebAuthn e ativar uma IU condicional

  1. No arquivo view/index.html, após o comentário relevante, substitua a instrução import atual por este código:

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
  $,
  _fetch,
  loading,
  authenticate
} from "/client.js";

Esse código importa a função authenticate() que você implementou anteriormente.

  1. Confirme se o objeto window.PulicKeyCredential está disponível e se o método PublicKeyCredential.isConditionalMediationAvailable() retorna um valor true e, em seguida, chame a função authenticate():

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
if (
  window.PublicKeyCredential &&
  PublicKeyCredential.isConditionalMediationAvailable
) {
  try {

    // Is conditional UI available in this browser?
    const cma =
      await PublicKeyCredential.isConditionalMediationAvailable();
    if (cma) {

      // If conditional UI is available, invoke the authenticate() function.
      const user = await authenticate();
      if (user) {

        // Proceed only when authentication succeeds.
        $("#username").value = user.username;
        loading.start();
        location.href = "/home";
      } else {
        throw new Error("User not found.");
      }
    }
  } catch (e) {
    loading.stop();

    // A NotAllowedError indicates that the user canceled the operation.
    if (e.name !== "NotAllowedError") {
      console.error(e);
      alert(e.message);
    }
  }
}

Revisar o código da solução para esta seção

view/index.html

<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
  type="text"
  id="username"
  class="mdc-text-field__input"
  aria-labelledby="username-label"
  name="username"
  autocomplete="username webauthn"
  autofocus
/>

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
  $,
  _fetch,
  loading,
  authenticate
} from '/client.js';

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
// Is WebAuthn avaiable in this browser?
if (window.PublicKeyCredential &&
    PublicKeyCredential.isConditionalMediationAvailable) {
  try {

    // Is a conditional UI available in this browser?
    const cma= await PublicKeyCredential.isConditionalMediationAvailable();
    if (cma) {

      // If a conditional UI is available, invoke the authenticate() function.
      const user = await authenticate();
      if (user) {

        // Proceed only when authentication succeeds.
        $('#username').value = user.username;
        loading.start();
        location.href = '/home';
      } else {
        throw new Error('User not found.');
      }
    }
  } catch (e) {
    loading.stop();

    // A NotAllowedError indicates that the user canceled the operation.
    if (e.name !== 'NotAllowedError') {
      console.error(e);
      alert(e.message);
    }
  }
}

Faça um teste

Você implementou a criação, o registro, a exibição e a autenticação de chaves de acesso no seu site.

Para testar, siga estas etapas:

  1. Navegue até a guia de visualização.
  2. Se necessário, saia da conta.
  3. Clique na caixa de texto do nome do usuário. Uma caixa de diálogo será exibida.
  4. Selecione a conta em que você quer fazer login.
  5. Verifique sua identidade com o bloqueio de tela do dispositivo. Você será redirecionado para a página /home e conectado.

Uma caixa de diálogo que solicita a verificação da sua identidade com uma senha ou chave de acesso salvas.

7. Parabéns!

Você concluiu este codelab. Em caso de dúvidas, faça perguntas na lista de e-mails FIDO-DEV ou no StackOverflow com uma tag passkey.

Saiba mais