Este guia detalha como implementar a autenticação de usuários recorrentes usando credenciais detectáveis, tanto por acionadores de botão explícitos quanto por sugestões de preenchimento automático do navegador (IU condicional).
Lado do servidor
Geração de opções
Crie um endpoint que gere parâmetros de solicitação da WebAuthn usando uma biblioteca aprovada de acordo com os padrões.
- Usar o ID de RP predefinido: use o ID de RP adequado predefinido como uma string constante.
- Gerar desafio: gere um buffer aleatório criptograficamente seguro e de alta entropia, armazene-o com segurança na sessão do usuário e codifique-o como Base64URL.
- Mapeamento de credenciais detectáveis: especifique uma matriz vazia
[]paraallowCredentials. Isso solicita credenciais detectáveis, o que significa que o usuário não precisa inserir o nome de usuário primeiro. O provedor de chaves de acesso vai apresentar as contas disponíveis. - Nível de verificação do usuário: defina
userVerification: "preferred"(ou"required"se exigido explicitamente pelas políticas de compliance corporativo).- O nível de restrição
userVerificationsolicitado PRECISA ser mantido no registro da sessão do servidor no endpoint de opções, em vez de ser transmitido de volta pelo cliente usando strings de consulta. Isso permite que o endpoint de verificação aplique restrições de correspondência estritas com segurança, sem risco de manipulação do cliente.
- O nível de restrição
// Options generation example (discoverable flow)
const options = {
challenge: serverGeneratedBase64UrlChallenge, // High-entropy random challenge stored in session
rpId: "example.com",
allowCredentials: [], // Request discoverable passkeys
userVerification: "preferred",
};
// Persist expected UV level to user session
req.session.expectedUserVerification = "preferred";
Endpoint de verificação
Verifique com segurança a declaração retornada pelo cliente para autenticar o usuário:
- Validar desafio de sessão: aplique uma correspondência estrita de desafio entre a resposta do cliente e o desafio esperado armazenado na sessão.
- Aplicar preferências de UV:
- Permita autenticadores sem UV (por exemplo, bloqueios de tela do autenticador desativados) se o
expectedUserVerificationda sessão solicitar"preferred", transmitindorequireUserVerification: falsepara sua biblioteca de verificação do lado do servidor. Se solicitado"required", aplique a biometria/PIN de forma rigorosa.
- Permita autenticadores sem UV (por exemplo, bloqueios de tela do autenticador desativados) se o
- Erro de servidor limpo 404: se o ID da credencial retornado pelo cliente não for encontrado no banco de dados, retorne um erro HTTP
404explícito para que o cliente possa acionar a API Signal.
Lógica do lado do cliente
Anotação de formulário HTML
Anote as entradas de nome de usuário e senha para usar a interface condicional de forma nativa. Os tokens de preenchimento automático combinam os parâmetros de especificação do webauthn, e o foco automático aciona o pop-up de preenchimento automático do navegador imediatamente quando a entrada é focada.
<!-- Autocomplete tokens must contain webauthn space-separated -->
<form id="signin-form">
<input
type="text"
name="username"
autocomplete="username webauthn"
autofocus
data-testid="username-field"
/>
<input type="password" name="password" autocomplete="current-password" />
<button type="submit">Sign in</button>
</form>
Fluxo de botão explícito
Acione a autenticação por chave de acesso quando um usuário clicar no botão "Fazer login com chave de acesso". Interrompa as chamadas de preenchimento automático de formulários (Conditional Get) em andamento antes de invocar o prompt da chave de acesso.
Fluxo de mediação condicional (preenchimento automático de formulários)
Ative as sugestões de preenchimento automático de formulários no carregamento da página para oferecer autenticação de chave de acesso de forma nativa quando os usuários se concentrarem nos campos de login:
- Detecção de recursos: chame
PublicKeyCredential.getClientCapabilities()no carregamento da página e pule o login com chave de acesso seconditionalGetnão estiver disponível. - Opções de decodificação: decodifique o objeto JSON de credencial buscado com
PublicKeyCredential.parseRequestOptionsFromJSON(). - Invocar o Get condicional: chame
navigator.credentials.get()commediation: "conditional"e transmita um indicadorAbortController. Isso registra o preenchimento automático de forma silenciosa, sem renderizar uma caixa de diálogo de chave de acesso. - Segregação de exceções Try/Catch: envolva a chamada
navigator.credentials.getem um bloco try/catch:NotAllowedError: o usuário cancelou ou o tempo limite do prompt de login da chave de acesso expirou.AbortError: a solicitação de autenticação foi cancelada de forma programática.
- API Call Signal: envolva a chamada de verificação do servidor
fetch()em um bloco try/catch:- Mostre uma mensagem de erro para que o usuário entenda o que deu errado.
- Chame
signalUnknownCredential()SOMENTE quando o servidor responder explicitamente com o status HTTP404(credencial não encontrada) e o usuário não estiver autenticado. - O parâmetro
credentialIdtransmitido parasignalUnknownCredential()PRECISA ser estritamente a string de ID da credencial codificada em Base64URL (por exemplo,encoded.id), NÃO o objeto ArrayBuffer brutocredential.rawId.
- Codificar a resposta: codifique a credencial
AuthenticatorAssertionResponsecom.toJSON()antes de enviá-la ao servidor para verificação.
// optionsFetch and loginVerifyFetch are app-defined HTTP methods
import { optionsFetch, loginVerifyFetch } from "./api.js";
let autofillAbortController = new AbortController();
async function initializeConditionalAutofill() {
// Feature detect Conditional Get autofill support
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet === true) {
const loginOptionsJSON = await optionsFetch();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);
try {
// Initiate Conditional UI form autofill suggestions
const credential = await navigator.credentials.get({
publicKey,
signal: autofillAbortController.signal,
mediation: "conditional",
});
// Segregated verification fetch
const encoded = credential.toJSON();
const response = await loginVerifyFetch(encoded);
if (!response.ok && response.status === 404) {
// Note: this code path runs pre-authentication, satisfying the unauth precondition
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encoded.id,
});
}
}
} catch (err) {
// Silently swallow expected client WebAuthn exceptions
if (["NotAllowedError", "AbortError"].includes(err.name)) {
return;
}
console.error("Unexpected conditional get error:", err);
}
}
}
async function triggerButtonAuthentication() {
// Abort any pending Conditional Get call to prevent passkey prompt collisions
autofillAbortController.abort();
autofillAbortController = new AbortController(); // Reset controller for next triggers
const loginOptionsJSON = await optionsFetch();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);
let credential;
try {
// Passkey explicit prompt trigger
credential = await navigator.credentials.get({
publicKey,
signal: autofillAbortController.signal,
});
} catch (err) {
if (err.name === "NotAllowedError") {
console.log("User cancelled passkey login.");
} else if (err.name === "AbortError") {
console.log("The authentication operation was aborted.");
}
// Re-arm Conditional autofill Suggestions after cancelled explicit button prompts
initializeConditionalAutofill();
return; // Safe exit
}
// Segregated verification try/catch (HTTP 404 trigger)
const encoded = credential.toJSON();
try {
const response = await loginVerifyFetch(encoded);
if (!response.ok && response.status === 404) {
// Note: this code path runs pre-authentication, satisfying the unauth precondition
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encoded.id, // Base64URL-encoded credential ID
});
}
} catch (serverErr) {
console.error("Verification request error:", serverErr);
}
}
// Trigger Conditional Get on load
window.addEventListener("DOMContentLoaded", initializeConditionalAutofill);
Estratégias de fallback
Substituto para detecção de recursos de chave de acesso
Sempre instale "webauthn-polyfills" e importe-o no contexto.
Se PublicKeyCredential for compatível, PublicKeyCredential.getClientCapabilities também será.
import 'webauthn-polyfills';
Substituição de sincronização da API Signal
A API WebAuthn Signal (webauthn-signals) é uma otimização progressiva usada para manter os gerenciadores de senhas sincronizados com o estado das credenciais do servidor.
- Experiência de fallback: restrita por
if (PublicKeyCredential.signalUnknownCredential). Se não houver suporte, a sincronização de verificação em segundo plano será ignorada sem gerar exceções do navegador.
Easy JSON Serialization Fallback
Sempre instale "webauthn-polyfills" e importe-o no contexto.
Se PublicKeyCredential for compatível, PublicKeyCredential.parseRequestOptionsFromJSON e PublicKeyCredential.prototype.toJSON também serão.
import 'webauthn-polyfills';