Visão geral
Confira uma visão geral das principais etapas envolvidas no registro de uma chave de acesso:

- Defina as opções para criar uma chave de acesso. Envie-as ao cliente para que possam ser transmitidas à chamada de criação da chave de acesso: a chamada de API WebAuthn
navigator.credentials.createna Web ecredentialManager.createCredentialno Android. Depois que o usuário confirma a criação da chave de acesso, a chamada de criação é resolvida e retorna uma credencialPublicKeyCredential. - Verifique a credencial e armazene-a no servidor.
As seções a seguir detalham cada etapa.
Criar opções de criação de credenciais
A primeira etapa que você precisa realizar no servidor é criar um objeto PublicKeyCredentialCreationOptions.
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, generateRegistrationOptions.
PublicKeyCredentialCreationOptions precisa incluir tudo o que é necessário para a criação da chave de acesso: informações sobre o usuário, sobre a parte confiável e uma configuração para as propriedades da credencial que está sendo criada. Depois de definir tudo isso, transmita-os conforme necessário para a função na biblioteca do lado do servidor do FIDO responsável por criar o objeto PublicKeyCredentialCreationOptions.
Alguns dos campos de PublicKeyCredentialCreationOptions podem ser constantes. Outros precisam ser definidos dinamicamente no servidor:
rpId: para preencher o ID de RP no servidor, use funções ou variáveis do lado do servidor que forneçam o nome do host do seu aplicativo da Web, comoexample.com.user.nameeuser.displayName: para preencher esses campos, use as informações da sessão do usuário conectado ou as informações da nova conta de usuário, se o usuário estiver criando uma chave de acesso ao se inscrever.user.namegeralmente é um endereço de e-mail e é exclusivo para a parte confiável.user.displayNameé um nome fácil de usar. Nem todas as plataformas usamdisplayName.user.id: uma string aleatória e exclusiva gerada na criação da conta. Ele precisa ser permanente, ao contrário de um nome de usuário, que pode ser editável. O ID do usuário identifica uma conta, mas não pode conter informações de identificação pessoal (PII). Provavelmente, você já tem um ID do usuário no seu sistema, mas, se necessário, crie um especificamente para chaves de acesso para que ele não tenha PIIs.excludeCredentials: uma lista de IDs de credenciais para evitar a duplicação de uma chave de acesso do provedor. Para preencher esse campo, procure no banco de dados as credenciais existentes para esse usuário. Confira os detalhes em Evitar a criação de uma nova chave de acesso se já houver uma.challenge: para o registro de credenciais, o desafio não é relevante, a menos que você use o atestado, uma técnica mais avançada para verificar a identidade de um provedor de chaves de acesso e os dados que ele emite. No entanto, mesmo que você não use o atestado, o desafio ainda é um campo obrigatório. As instruções para criar um desafio seguro para autenticação estão disponíveis em Autenticação de chave de acesso do lado do servidor.
Codificação e decodificação
PublicKeyCredentialCreationOptions enviado pelo servidor. Campos binários como challenge e user.id são codificados do lado do servidor em base64URL para que as opções possam ser entregues como JSON por HTTPS.O objeto PublicKeyCredentialCreationOptions padrão do WebAuthn inclui campos que são ArrayBuffers, que não podem ser serializados diretamente com JSON.stringify(). Para enviar essas opções por HTTPS, os campos binários precisam ser codificados como strings base64URL:
- No servidor, sua biblioteca do lado do servidor FIDO (como SimpleWebAuthn) produz automaticamente um objeto de opções serializável em JSON (por exemplo,
PublicKeyCredentialCreationOptionsJSON) com campos codificados em Base64URL. - No cliente, os navegadores mais recentes são compatíveis com
PublicKeyCredential.parseCreationOptionsFromJSON()para converter as opções JSON diretamente emPublicKeyCredentialCreationOptions. Ao usar bibliotecas do lado do cliente, como@simplewebauthn/browser, funções comostartRegistration({ optionsJSON: options })processam essa conversão automaticamente.
Exemplo de código: criar opções de criação de credenciais
Estamos usando a biblioteca SimpleWebAuthn nos nossos exemplos. Aqui, entregamos a criação de opções de credenciais de chave pública para a função generateRegistrationOptions.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
} from '@simplewebauthn/server';
import { isoBase64URL, isoUint8Array } from '@simplewebauthn/server/helpers';
router.post('/registerRequest', csrfCheck, sessionCheck, async (req, res) => {
const { user } = res.locals;
// 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 {
// `excludeCredentials` prevents users from re-registering existing
// credentials for a given passkey provider
const excludeCredentials = [];
const credentials = Credentials.findByUserId(user.id);
if (credentials.length > 0) {
for (const cred of credentials) {
excludeCredentials.push({
id: cred.id,
type: 'public-key',
transports: cred.transports,
});
}
}
// Generate registration options for WebAuthn create
const options = await generateRegistrationOptions({
rpName: process.env.RP_NAME,
rpID: process.env.HOSTNAME,
userID: isoUint8Array.fromUTF8String(user.id),
userName: user.username,
userDisplayName: user.displayName || '',
attestationType: 'none',
excludeCredentials,
authenticatorSelection: {
authenticatorAttachment: 'platform',
residentKey: 'required',
userVerification: 'preferred',
},
});
// Keep the challenge in the session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).send({ error: e.message });
}
});
Armazenar a chave pública
navigator.credentials.create retorna um objeto PublicKeyCredential.Quando navigator.credentials.create é resolvido com sucesso no cliente, significa que uma chave de acesso foi criada. Um objeto PublicKeyCredential é retornado.
O objeto PublicKeyCredential contém um objeto AuthenticatorAttestationResponse, que representa a resposta do provedor de chaves de acesso à instrução do cliente para criar uma chave de acesso. Ele contém informações sobre a nova credencial que você precisa como RP para autenticar o usuário mais tarde. Saiba mais sobre o AuthenticatorAttestationResponse no Apêndice: AuthenticatorAttestationResponse.
Envie o objeto PublicKeyCredential para o servidor. Depois de receber o código, faça a verificação.
Transfira essa etapa de verificação para a biblioteca do lado do servidor FIDO. Normalmente, ela oferece uma função utilitária para essa finalidade. O SimpleWebAuthn oferece, por exemplo, verifyRegistrationResponse. Saiba o que acontece nos bastidores no Apêndice: verificação da resposta de registro.
Depois que a verificação for concluída, armazene as informações da credencial no banco de dados para que o usuário possa se autenticar com a chave de acesso associada a essa credencial.
Use uma tabela dedicada para credenciais de chave pública associadas a chaves de acesso. Um usuário só pode ter uma senha, mas pode ter várias chaves de acesso, por exemplo, uma sincronizada pelo iCloud Keychain da Apple e outra pelo Gerenciador de Senhas do Google.
Veja um exemplo de esquema que pode ser usado para armazenar informações de credenciais:

- Tabela Usuários:
user_id: o ID do usuário principal. Um ID aleatório, exclusivo e permanente para o usuário. Use-o como uma chave primária para sua tabela Users.username. Um nome de usuário definido pelo usuário, possivelmente editável.passkey_user_id: o ID do usuário específico da chave de acesso sem PII, representado poruser.idnas suas opções de registro. Quando o usuário tentar se autenticar, o autenticador vai disponibilizar essepasskey_user_idna resposta de autenticação emuserHandle. Recomendamos que você não definapasskey_user_idcomo uma chave primária. As chaves primárias tendem a se tornar PII de fato nos sistemas, porque são amplamente usadas.
- Tabela de credenciais de chave pública:
id: ID da credencial. Use-o como uma chave primária para a tabela Credenciais de chave pública.public_key: chave pública da credencial.passkey_user_id: use como uma chave externa para estabelecer um link com a tabela Users.backed_up: uma chave de acesso é armazenada em backup se for sincronizada pelo provedor de chaves de acesso. Armazenar o estado de backup é útil se você quiser considerar a possibilidade de descartar senhas no futuro para usuários que têm chaves de acesso dobacked_up. Para verificar se o backup da chave de acesso foi feito, examine a flag BE emauthenticatorDataou use um recurso da biblioteca do lado do servidor FIDO, que geralmente está disponível para facilitar o acesso a essas informações. Armazenar a qualificação para backup pode ser útil para responder a possíveis dúvidas dos usuários.name: opcionalmente, um nome de exibição para a credencial para que os usuários possam dar nomes personalizados às credenciais.transports: uma matriz de transportes. Armazenar transportes é útil para a experiência do usuário de autenticação. Quando os transportes estão disponíveis, o navegador pode se comportar de acordo e mostrar uma interface que corresponde ao transporte usado pelo provedor de chaves de acesso para se comunicar com os clientes, principalmente em casos de uso de reautenticação em queallowCredentialsnão está vazio.
Outras informações podem ser úteis para armazenar para fins de experiência do usuário, incluindo itens como o provedor de chaves de acesso, o horário de criação da credencial e o horário do último uso. Leia mais em Design da interface do usuário de chaves de acesso.
Exemplo de código: armazenar a credencial
Estamos usando a biblioteca SimpleWebAuthn nos nossos exemplos.
Aqui, entregamos a verificação da resposta de registro à função verifyRegistrationResponse.
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/registerResponse', csrfCheck, sessionCheck, async (req, res) => {
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
const response = req.body;
// This sample code is for registering a passkey for an existing,
// signed-in user
// 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 {
// Verify the credential
const { verified, registrationInfo } = await verifyRegistrationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
requireUserVerification: false,
});
if (!verified) {
throw new Error('Verification failed.');
}
const {
aaguid,
credential,
credentialBackedUp
} = registrationInfo;
// Name the credential based on AAGUID
const name =
aaguid === undefined ||
aaguid === '00000000-0000-0000-0000-000000000000' ?
req.useragent?.platform : aaguids[aaguid]?.name;
const base64PublicKey = isoBase64URL.fromBuffer(credential.publicKey);
// Existing, signed-in user
const { user } = res.locals;
// Save the credential
await Credentials.update({
id: credential.id,
passkey_user_id: user.passkey_user_id,
publicKey: base64PublicKey,
name,
aaguid,
transports: credential.transports || response.response.transports,
backed_up: credentialBackedUp,
registered_at: new Date().getTime()
});
// Kill the challenge for this session
delete req.session.challenge;
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).send({ error: e.message });
}
});
Apêndice: AuthenticatorAttestationResponse
AuthenticatorAttestationResponse contém dois objetos importantes:
response.clientDataJSONé uma versão JSON dos dados do cliente, que na Web são os dados vistos pelo navegador. Ele contém a origem da RP, o desafio eandroidPackageNamese o cliente for um app Android. Como RP, a leitura declientDataJSONdá acesso às informações que o navegador tinha no momento da solicitaçãocreate.response.attestationObjectcontém duas informações:attestationStatement, que não é relevante, a menos que você use o atestado.authenticatorDatasão os dados como vistos pelo provedor de chaves de acesso. Como RP, lerauthenticatorDatadá acesso aos dados vistos pelo provedor de chaves de acesso e retornados no momento da solicitaçãocreate.
authenticatorDatacontém informações essenciais sobre a credencial de chave pública associada à chave de acesso recém-criada:
- A credencial de chave pública em si e um ID de credencial exclusivo para ela.
- O ID da parte restrita associado à credencial.
- Flags que descrevem o status do usuário quando a chave de acesso foi criada: se um usuário estava realmente presente e se ele foi verificado (consulte Investigação de userVerification).
- O AAGUID é um identificador do provedor de chaves de acesso, como o Gerenciador de Senhas do Google. Com base no AAGUID, é possível identificar o provedor da chave de acesso e mostrar o nome em uma página de gerenciamento de chaves de acesso. (consulte Determinar o provedor de chaves de acesso com o AAGUID)
Embora authenticatorData esteja aninhado em attestationObject, as informações que ele contém são necessárias para a implementação da chave de acesso, independente do uso de atestado. authenticatorData é codificado e contém campos codificados em um formato binário. A biblioteca do lado do servidor geralmente lida com a análise e a decodificação. Se você não estiver usando uma biblioteca do lado do servidor, considere usar o getAuthenticatorData() do lado do cliente para economizar trabalho de análise e decodificação do lado do servidor.
Apêndice: verificação da resposta de registro
Nos bastidores, a verificação da resposta de registro consiste nas seguintes verificações:
- Verifique se o ID da parte confiável corresponde ao seu site.
- Verifique se a origem da solicitação é esperada para seu site (URL do site principal, app Android).
- Se você exigir a verificação do usuário, verifique se a flag de verificação do usuário
authenticatorData.uvétrue. - A flag de presença do usuário
authenticatorData.upgeralmente étrue, mas se a credencial for criada condicionalmente, ela seráfalse. - Verifique se o cliente conseguiu fornecer o desafio que você deu a ele. Se você não usa o atestado, essa verificação não é importante. No entanto, implementar essa verificação é uma prática recomendada: ela garante que seu código esteja pronto se você decidir usar o atestado no futuro.
- Verifique se o ID da credencial ainda não está registrado para nenhum usuário.
- Verifique se o algoritmo usado pelo provedor de chaves de acesso para criar a credencial é um dos que você listou (em cada campo
algdepublicKeyCredentialCreationOptions.pubKeyCredParams, que normalmente é definido na biblioteca do lado do servidor e não fica visível para você). Isso garante que os usuários só possam se registrar com algoritmos que você permitiu.
Para saber mais, confira o código-fonte do SimpleWebAuthn para verifyRegistrationResponse ou consulte a lista completa de verificações na especificação.