Visão geral
Confira uma visão geral de alto nível das principais etapas envolvidas na autenticação da chave de acesso:
- Defina o desafio e outras opções necessárias para autenticar com uma chave de acesso. Envie-as ao cliente para que você possa transmiti-las à chamada de autenticação da chave de acesso (
navigator.credentials.get
na 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
), que 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 mergulham nos detalhes de 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 sua finalidade, você deve:
- Garanta que o mesmo desafio nunca seja usado mais de uma vez. Gerar um novo desafio a cada tentativa de login. Descarte o desafio depois de cada tentativa de login, seja ela bem-sucedida ou não. Descartar o desafio após uma certa duração também. Nunca aceite o mesmo desafio em uma resposta mais de uma vez.
- Garanta que o desafio seja criptograficamente seguro. Um desafio é praticamente impossível de adivinhar. Para criar um desafio no lado do servidor com criptografia segura, recomendamos que você use uma biblioteca FIDO confiável no lado do servidor. Se você criar seus próprios desafios, use a funcionalidade criptográfica integrada do seu conjunto de tecnologias ou procure bibliotecas projetadas para casos de uso de criptografia. Por exemplo, iso-crypto no Node.js ou secrets em Python. De acordo com a especificação, o desafio precisa ter pelo menos 16 bytes para ser considerado seguro.
Após criar um desafio, salve-o na sessão do usuário para verificá-lo mais tarde.
Criar opções de solicitação de credencial
Crie opções de solicitação de credencial como um objeto publicKeyCredentialRequestOptions
.
Para isso, use sua biblioteca FIDO do lado do servidor. Normalmente, ele 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 da chave de acesso. Transmita essas informações para a função na biblioteca FIDO do lado do servidor responsável por criar o objeto publicKeyCredentialRequestOptions
.
Alguns dos campos da publicKeyCredentialRequestOptions
podem ser constantes. Outras precisam ser definidas dinamicamente no servidor:
rpId
: a qual ID da RP você espera que a credencial seja associada, por exemplo,example.com
. A autenticação só será bem-sucedida se o ID da RP fornecido aqui corresponder ao ID da RP associado à credencial. Para preencher o ID da RP, use o mesmo valor que o ID da RP definido empublicKeyCredentialCreationOptions
durante o registro da credencial.challenge
: um dado que o provedor da chave de acesso vai assinar para provar que o usuário tem a chave de acesso no momento da solicitação de autenticação. Analise os detalhes em Criar o desafio.allowCredentials
: uma matriz de credenciais aceitáveis para esta 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. Para saber mais, consulte Buscar um desafio do servidor da parte restrita e Análise detalhada de credenciais detectáveis.userVerification
: indica se a verificação do usuário com o bloqueio de tela do dispositivo é "obrigatória", "preferida" ou "não recomendada". Consulte Buscar um desafio no servidor da RP.timeout
: quanto tempo (em milissegundos) o usuário pode levar para concluir a autenticação. Ele precisa ser razoavelmente generoso e menor que o ciclo de vida dochallenge
. O valor padrão recomendado é 5 minutos, mas é possível aumentá-lo até 10 minutos, que ainda está dentro do intervalo recomendado. Tempos limite longos são úteis se você espera que os usuários utilizem o fluxo de trabalho híbrido, que normalmente demora um pouco mais. Se a operação expirar, umaNotAllowedError
será gerada.
Depois de criar publicKeyCredentialRequestOptions
, envie-o para o cliente.
![publicKeyCredentialCreationOptions enviada pelo servidor](https://developers-dot-devsite-v2-prod.appspot.com/identity/passkeys/developer-guides/server-authentication/images/auth-options.png?authuser=6&hl=pt)
challenge
acontece no lado do cliente.Exemplo de código: criar opções de solicitação de credencial
Vamos usar a biblioteca SimpleWebAuthn (link em inglês) nos exemplos. Aqui, passamos a criação de opções de solicitação de credencial para a função generateAuthenticationOptions
.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} 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
.
![Objeto PublicKeyCredential enviado pelo servidor](https://developers-dot-devsite-v2-prod.appspot.com/identity/passkeys/developer-guides/server-authentication/images/publicKeyCredential.png?authuser=6&hl=pt)
navigator.credentials.get
retorna uma PublicKeyCredential
.O response
é um AuthenticatorAssertionResponse
. Ela representa a resposta do provedor da chave 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.authenticatorData
eresponse.clientDataJSON
, como na etapa de registro da chave de acesso.response.signature
, que contém uma assinatura sobre esses valores.
Envie o objeto PublicKeyCredential
ao servidor.
No servidor, faça o seguinte:
![Esquema do banco de dados](https://developers-dot-devsite-v2-prod.appspot.com/identity/passkeys/developer-guides/server-authentication/images/database-schema-passkeys.png?authuser=6&hl=pt)
- Colete as informações necessárias para verificar a declaração e autenticar o usuário:
- Encontre o desafio esperado que você armazenou na sessão quando gerou as opções de autenticação.
- Consiga a origin e o ID da RP esperados.
- Encontre no seu 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. Você tem duas opções para descobrir:
- Opção 1: use o
response.userHandle
no objetoPublicKeyCredential
. Na tabela Usuários, procure apasskey_user_id
que corresponde auserHandle
. - Opção 2: use a credencial
id
presente no objetoPublicKeyCredential
. Na tabela Credenciais de chave pública, procure a credencialid
que corresponde à credencialid
presente no objetoPublicKeyCredential
. Em seguida, procure o usuário correspondente usando a chave estrangeirapasskey_user_id
na sua tabela Usuários.
- Opção 1: use o
- Encontre no seu banco de dados as informações das credenciais de chave pública que correspondem à declaração de autenticação que você recebeu. Para fazer isso, na tabela Public key credentials, procure a credencial
id
que corresponde à credencialid
presente no objetoPublicKeyCredential
.
Verifique a declaração de autenticação. Entregue essa etapa de verificação à sua biblioteca FIDO do lado do servidor, que normalmente oferecerá uma função utilitária para essa finalidade. O SimpleWebAuthn oferece, por exemplo,
verifyAuthenticationResponse
. Saiba o que acontece nos bastidores em Apêndice: verificação da resposta de autenticação.Exclua o desafio independentemente de a verificação ter sido bem-sucedida ou não para evitar ataques repetidos.
Faça o login do usuário. Se a verificação foi concluída, atualize as informações da sessão para marcar o usuário como conectado. Também é possível retornar um objeto
user
ao 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
Vamos usar a biblioteca SimpleWebAuthn (link em inglês) nos exemplos. Aqui, entregamos a verificação da resposta de autenticação à função verifyAuthenticationResponse
.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
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.');
}
// Base64URL decode some values
const authenticator = {
credentialPublicKey: isoBase64URL.toBuffer(cred.publicKey),
credentialID: isoBase64URL.toBuffer(cred.id),
transports: cred.transports,
};
// Verify the credential
const { verified, authenticationInfo } = await verifyAuthenticationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
authenticator,
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 restrita corresponde ao seu site.
- Verifique se a origem da solicitação corresponde à origem de login do seu site. No caso de apps Android, consulte Verificar origem.
- Verifique se o dispositivo foi capaz de apresentar o desafio que você apresentou.
- Verifique se, durante a autenticação, o usuário seguiu os requisitos que você determina como RP. Se você exigir a verificação do usuário, confira se a sinalização
uv
(verificado pelo usuário) emauthenticatorData
étrue
. Verifique se a flagup
(presente do usuário) 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 com que verificar a assinatura.
- Os dados originais assinados. Esses 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, consulte o código-fonte de verifyAuthenticationResponse
do SimpleWebAuthn ou veja a lista completa de verificações na especificação.