Visão geral
Confira uma visão geral das principais etapas envolvidas na autenticação com chave de acesso:

- Defina o desafio e outras opções necessárias para autenticar com uma chave de acesso. Envie-os ao cliente para que você possa transmiti-los à chamada de autenticação da chave de acesso (
navigator.credentials.getna Web). Depois que o usuário confirma a autenticação da chave de acesso, a chamada de autenticação é resolvida e retorna uma credencial (PublicKeyCredential). A credencial contém uma declaração de autenticação.
- Verifique a declaração de autenticação.
- Se a declaração de autenticação for válida, autentique o usuário.
As seções a seguir detalham cada etapa.
Criar o desafio
Na prática, um desafio é uma matriz de bytes aleatórios, representada como um objeto ArrayBuffer.
// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8
Para garantir que o desafio cumpra o propósito, você precisa:
- Garanta que o mesmo desafio nunca seja usado mais de uma vez. Gere um novo desafio a cada tentativa de login. Descarte o desafio após cada tentativa de login, seja ela bem-sucedida ou não. Descarte o desafio após um determinado período. Nunca aceite o mesmo desafio em uma resposta mais de uma vez.
- Verifique se o desafio é criptograficamente seguro. Um desafio deve ser praticamente impossível de adivinhar. Para criar um desafio criptograficamente seguro no lado do servidor, é melhor usar uma biblioteca FIDO confiável do lado do servidor. Se você criar seus próprios desafios, use a funcionalidade criptográfica integrada disponível na sua pilha de tecnologia ou procure bibliotecas projetadas para casos de uso criptográficos. Por exemplo, iso-crypto em Node.js ou secrets em Python. De acordo com a especificação, o desafio precisa ter pelo menos 16 bytes para ser considerado seguro.
Depois de criar um desafio, salve-o na sessão do usuário para verificação posterior.
Criar opções de solicitação de credencial
Crie opções de solicitação de credencial como um objeto publicKeyCredentialRequestOptions.
Para isso, use a biblioteca do lado do servidor FIDO. Normalmente, ela oferece uma função utilitária que pode criar essas opções para você. O SimpleWebAuthn oferece, por exemplo, generateAuthenticationOptions.
publicKeyCredentialRequestOptions precisa conter todas as informações necessárias para a autenticação com chave de acesso. Transmita essas informações para a função na biblioteca do lado do servidor FIDO responsável por criar o objeto publicKeyCredentialRequestOptions.
Alguns dos campos de publicKeyCredentialRequestOptions podem ser constantes. Outros precisam ser definidos dinamicamente no servidor:
rpId: o ID da parte confiável que você espera que seja associado à credencial, por exemplo,example.com. A autenticação só vai funcionar se o ID da parte confiável fornecido aqui corresponder ao ID da parte confiável associado à credencial. Para preencher o ID da RP, use o mesmo valor que você definiu empublicKeyCredentialCreationOptionsdurante o registro da credencial.challenge: um dado que o provedor de chaves de acesso vai assinar para provar que o usuário tem a chave de acesso no momento da solicitação de autenticação. Confira os detalhes em Criar o desafio.allowCredentials: uma matriz de credenciais aceitáveis para essa autenticação. Transmita uma matriz vazia para permitir que o usuário selecione uma chave de acesso disponível em uma lista mostrada pelo navegador. Consulte Buscar um desafio do servidor RP e Investigação detalhada das credenciais detectáveis para mais detalhes.userVerification: indica se a verificação do usuário usando o bloqueio de tela do dispositivo é "required", "preferred" ou "discouraged". Consulte Buscar um desafio no servidor da parte confiável.timeout: quanto tempo (em milissegundos) o usuário tem para concluir a autenticação. Ele precisa ser razoavelmente generoso e menor que o ciclo de vida dochallenge. O valor padrão recomendado é de 5 minutos, mas é possível aumentá-lo para até 10 minutos, o que ainda está dentro do intervalo recomendado. Os tempos limite longos fazem sentido se você espera que os usuários usem o fluxo de trabalho híbrido, que normalmente leva um pouco mais de tempo. Se a operação atingir o tempo limite, umaNotAllowedErrorserá gerada.
Depois de criar o publicKeyCredentialRequestOptions, envie-o ao cliente.
challenge ocorre no lado do cliente.Exemplo de código: criar opções de solicitação de credencial
Estamos usando a biblioteca SimpleWebAuthn nos nossos exemplos. Aqui, entregamos a criação de opções de solicitação de credencial para a função generateAuthenticationOptions.
import {
generateAuthenticationOptions,
} from '@simplewebauthn/server';
router.post('/signinRequest', csrfCheck, async (req, res) => {
// Ensure you nest calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Use the generateAuthenticationOptions function from SimpleWebAuthn
const options = await generateAuthenticationOptions({
rpID: process.env.HOSTNAME,
allowCredentials: [],
});
// Save the challenge in the user session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Verificar e fazer login do usuário
Quando navigator.credentials.get é resolvido com sucesso no cliente, ele retorna um objeto PublicKeyCredential.
navigator.credentials.get retorna um PublicKeyCredential.O response é um AuthenticatorAssertionResponse. Ela representa a resposta do provedor de chaves de acesso à instrução do cliente para criar o que é necessário para tentar autenticar com uma chave de acesso na RP. Ele contém:
response.authenticatorDataeresponse.clientDataJSON, como na etapa de registro da chave de acesso.response.signature, que contém uma assinatura desses valores.
Envie o objeto PublicKeyCredential para o servidor.
No servidor, faça o seguinte:
- Reúna as informações necessárias para verificar a declaração e autenticar o usuário:
- Recupere o desafio esperado que você armazenou na sessão ao gerar as opções de autenticação.
- Consiga a origem e o ID da RP esperados.
- Encontre no banco de dados quem é o usuário. No caso de credenciais detectáveis, você não sabe quem é o usuário que está fazendo uma solicitação de autenticação. Para descobrir, você tem duas opções:
- Opção 1: use o
response.userHandleno objetoPublicKeyCredential. Na tabela Usuários, procure opasskey_user_idque corresponde auserHandle. - Opção 2: use a credencial
idpresente no objetoPublicKeyCredential. Na tabela Credenciais de chave pública, procure a credencialidque corresponde à credencialidpresente no objetoPublicKeyCredential. Em seguida, procure o usuário correspondente usando a chave externapasskey_user_idpara a tabela Users.
- Opção 1: use o
- Encontre no banco de dados as informações de credencial de chave pública que correspondem à declaração de autenticação recebida. Para fazer isso, na tabela Credenciais de chave pública, procure a credencial
idque corresponde à credencialidpresente no objetoPublicKeyCredential.
Verifique a declaração de autenticação. Entregue essa etapa de verificação à biblioteca do lado do servidor FIDO, que normalmente oferece uma função utilitária para essa finalidade. O SimpleWebAuthn oferece, por exemplo,
verifyAuthenticationResponse. Saiba o que acontece nos bastidores no Apêndice: verificação da resposta de autenticação.Exclua o desafio, independentemente de a verificação ser bem-sucedida ou não, para evitar ataques de repetição.
Fazer o login do usuário. Se a verificação for bem-sucedida, atualize as informações da sessão para marcar o usuário como conectado. Também é possível retornar um objeto
userao cliente para que o front-end possa usar as informações associadas ao usuário recém-conectado.
Exemplo de código: verificar e fazer login do usuário
Estamos usando a biblioteca SimpleWebAuthn nos nossos exemplos. Aqui, entregamos a verificação da resposta de autenticação à função verifyAuthenticationResponse.
import {
verifyAuthenticationResponse,
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/signinResponse', csrfCheck, async (req, res) => {
const response = req.body;
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
// Ensure you nest verification function calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Find the credential stored to the database by the credential ID
const cred = Credentials.findById(response.id);
if (!cred) {
throw new Error('Credential not found.');
}
// Find the user - Here alternatively we could look up the user directly
// in the Users table via userHandle
const user = Users.findByPasskeyUserId(cred.passkey_user_id);
if (!user) {
throw new Error('User not found.');
}
// Prepare credential information for verification
const credential = {
id: cred.id,
publicKey: isoBase64URL.toBuffer(cred.publicKey),
transports: cred.transports,
};
// Verify the credential
const { verified, authenticationInfo } = await verifyAuthenticationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
credential,
requireUserVerification: false,
});
if (!verified) {
throw new Error('User verification failed.');
}
// Kill the challenge for this session.
delete req.session.challenge;
req.session.username = user.username;
req.session['signed-in'] = 'yes';
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Apêndice: verificação da resposta de autenticação
A verificação da resposta de autenticação consiste nas seguintes verificações:
- Verifique se o ID da parte confiável corresponde ao seu site.
- Verifique se a origem da solicitação corresponde à origem de login do seu site. Para apps Android, consulte Verificar origem.
- Verifique se o dispositivo conseguiu fornecer o desafio que você deu a ele.
- Verifique se, durante a autenticação, o usuário seguiu os requisitos que você exige como parte confiável. Se você exigir a verificação do usuário, confira se a flag
uv(usuário verificado) emauthenticatorDataétrue. Verifique se a flagup(usuário presente) emauthenticatorDataétrue, já que a presença do usuário é sempre necessária para chaves de acesso. - Verifique a assinatura. Para verificar a assinatura, você precisa do seguinte:
- A assinatura, que é o desafio assinado:
response.signature - A chave pública, para verificar a assinatura.
- Os dados originais assinados. São os dados cuja assinatura será verificada.
- O algoritmo criptográfico usado para criar a assinatura.
- A assinatura, que é o desafio assinado:
Para saber mais sobre essas etapas, confira o código-fonte do SimpleWebAuthn para verifyAuthenticationResponse ou consulte a lista completa de verificações na especificação.