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
- Noções básicas de JavaScript
- Conhecimentos básicos de chaves de acesso
- Noções básicas da API Web Authentication (WebAuthn)
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
- Abra o projeto no Glitch.
- Clique em Remix para bifurcar o projeto Glitch.
- 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.
Examinar o estado inicial do site
- Na guia de visualização, insira um nome de usuário aleatório e clique em Próxima.
- 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.
- Se quiser, mude seu nome de exibição. Isso é tudo o que você pode fazer no estado inicial.
- 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.
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()
- No Glitch, navegue até o arquivo
public/client.js
e role até o fim. - 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 |
Um desafio gerado pelo servidor em um objeto | |
ID exclusivo de um usuário. Esse valor precisa ser um objeto | |
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. | |
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. | |
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 | |
Esse campo especifica os algoritmos de chave pública compatíveis com a RP. Recomendamos defini-lo como | |
Mostra uma lista de IDs de credencial já registrados para evitar que o mesmo dispositivo seja registrado duas vezes. Se fornecido, o membro | |
Defina como um valor | |
Defina como um valor booleano | |
Defina-o como um valor de |
Criar uma credencial
- 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 stringsuser.id
echallenge
, e instâncias da stringid
incluídas na matrizexcludeCredentials
:
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);
}
}
- Na próxima linha, defina
authenticatorSelection.authenticatorAttachment
como"platform"
eauthenticatorSelection.requireResidentKey
comotrue
. 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
}
- 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 |
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. | |
Uma versão de objeto | |
Um objeto | |
Um objeto de atestado codificado por | |
Uma lista de transportes compatíveis com o dispositivo: | |
Retorna |
Para enviar o objeto da credencial para o servidor, siga estas etapas:
- 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
};
- 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.
Adicionar marcador de posição HTML
- No Glitch, navegue até o arquivo
views/home.html
. - 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:
- No final do arquivo
views/home.html
, após o comentário relevante, escreva uma condição que será executada sewindow.PublicKeyCredential
,PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable
ePublicKeyCredential.isConditionalMediationAvailable
foremtrue
.
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) {
- 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()
]);
- 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
- 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);
};
- 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:
- 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';
- No final do corpo da instrução
import
, adicione a funçãoregisterCredential()
.
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';
- No final do arquivo, após o comentário relevante, defina uma função
register()
que invoque a funçãoregisterCredential()
e uma IU de carregamento, além de chamarrenderCredentials()
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();
- No corpo da função
register()
, capture exceções. O métodonavigator.credentials.create()
gera um erroInvalidStateError
quando uma chave de acesso já existe no dispositivo. Isso é examinado com a matrizexcludeCredentials
. Nesse caso, mostre uma mensagem relevante para o usuário. Ele também gera um erroNotAllowedError
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);
}
}
};
- Na linha após a função
register()
, anexe a funçãoregister()
a um eventoclick
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:
- Na guia de visualização, faça login com um nome de usuário e uma senha aleatórios.
- Clique em Criar uma chave de acesso.
- Verifique sua identidade com o bloqueio de tela do dispositivo.
- Confirme se uma chave de acesso está registrada e é exibida na seção Suas chaves de acesso registradas da página da Web.
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 chamadaauthenticate()
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çãoPOST
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 |
Um desafio gerado pelo servidor em um objeto | |
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 | |
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. | |
Defina-o como um valor de |
Verificar o usuário localmente e receber uma credencial
- No corpo da função
authenticate()
após o comentário relevante, converta o parâmetrochallenge
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);
- 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.
- Chame o método
navigator.credentials.get()
com uma opçãomediation: '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 |
O ID codificado em Base64URL da credencial da chave de acesso autenticada. | |
Uma versão de objeto | |
Um objeto | |
Um objeto | |
Um objeto | |
Um objeto | |
Retorna uma string |
Para enviar o objeto da credencial para o servidor, siga estas etapas:
- 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,
};
- 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.
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
- No arquivo
view/index.html
, após o comentário relevante, substitua a instruçãoimport
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.
- Confirme se o objeto
window.PulicKeyCredential
está disponível e se o métodoPublicKeyCredential.isConditionalMediationAvailable()
retorna um valortrue
e, em seguida, chame a funçãoauthenticate()
:
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:
- Navegue até a guia de visualização.
- Se necessário, saia da conta.
- Clique na caixa de texto do nome do usuário. Uma caixa de diálogo será exibida.
- Selecione a conta em que você quer fazer login.
- Verifique sua identidade com o bloqueio de tela do dispositivo. Você será redirecionado para a página
/home
e conectado.
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
.