Questa guida spiega come implementare l'autenticazione degli utenti di ritorno utilizzando le credenziali rilevabili, sia tramite attivatori di pulsanti espliciti sia tramite suggerimenti di compilazione automatica del browser (UI condizionale).
Lato server
Generazione di opzioni
Crea un endpoint che genera parametri di richiesta WebAuthn utilizzando una libreria verificata in base agli standard.
- Utilizza l'ID RP predefinito: utilizza l'ID RP corretto predefinito come stringa costante.
- Genera la verifica: genera un buffer casuale ad alta entropia e crittograficamente sicuro, memorizzalo in modo sicuro nella sessione dell'utente e codificalo come Base64URL.
- Mappatura delle credenziali rilevabili: specifica un array vuoto
[]perallowCredentials. In questo modo vengono richieste credenziali rilevabili, il che significa che l'utente non deve inserire prima il proprio nome utente; il provider di passkey presenterà gli account disponibili. - Livello di verifica dell'utente: imposta
userVerification: "preferred"(o"required"se richiesto esplicitamente dalle policy di conformità aziendali).- Il livello di vincolo
userVerificationrichiesto DEVE essere mantenuto all'interno del record della sessione del server nell'endpoint delle opzioni, anziché essere restituito dal client tramite stringhe di query. In questo modo, l'endpoint di verifica può applicare in modo sicuro vincoli di corrispondenza rigorosi senza il rischio di manipolazione da parte del client.
- Il livello di vincolo
// 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 di verifica
Verifica in modo sicuro l'asserzione restituita dal client per autenticare l'utente:
- Convalida verifica sessione: applica una corrispondenza rigorosa della verifica tra la risposta del client e la verifica prevista memorizzata nella sessione.
- Applica le preferenze di verifica utente:
- Consenti gli autenticatori senza UV (ad es. i blocchi schermo dell'autenticatore disattivati) se la sessione
expectedUserVerificationha richiesto"preferred", passandorequireUserVerification: falsealla libreria di verifica lato server. Se richiesto"required", applica rigorosamente l'inserimento dei dati biometrici/del PIN.
- Consenti gli autenticatori senza UV (ad es. i blocchi schermo dell'autenticatore disattivati) se la sessione
- Errore del server 404 pulito: se l'ID credenziale restituito dal client non viene trovato nel database, restituisci un errore HTTP
404esplicito in modo che il client possa attivare l'API Signal.
Logica lato client
Annotazione di moduli HTML
Annota gli input di nome utente e password per sfruttare in modo nativo la UI condizionale. I token di completamento automatico combinano i parametri della specifica webauthn e l'autofocus attiva immediatamente il popup di compilazione automatica del browser quando l'input è attivo.
<!-- 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>
Flusso con pulsante esplicito
Attiva l'autenticazione con passkey quando un utente fa clic sul pulsante "Accedi con passkey". Interrompi tutte le chiamate di compilazione automatica dei moduli (Conditional Get) in corso prima di richiamare la richiesta di passkey.
Flusso di mediazione condizionale (compilazione automatica dei moduli)
Attiva i suggerimenti di compilazione automatica dei moduli al caricamento della pagina per offrire l'autenticazione con passkey in modo nativo quando gli utenti si concentrano sui campi di accesso:
- Rilevamento delle funzionalità: chiama
PublicKeyCredential.getClientCapabilities()al caricamento della pagina e salta l'accesso con la passkey seconditionalGetnon è disponibile. - Opzioni di decodifica: decodifica l'oggetto JSON delle credenziali recuperate con
PublicKeyCredential.parseRequestOptionsFromJSON(). - Richiama Conditional Get: chiama
navigator.credentials.get()conmediation: "conditional"e trasmetti un indicatoreAbortController. In questo modo, la compilazione automatica viene registrata in modo silenzioso senza visualizzare una finestra di dialogo della passkey. - Segregazione delle eccezioni Try/Catch: inserisci la chiamata
navigator.credentials.getin un blocco try/catch:NotAllowedError: l'utente ha annullato o è scaduto il tempo di attesa della richiesta di accesso con passkey.AbortError: la richiesta di autenticazione è stata annullata in modo programmatico.
- Chiama l'API Signal: racchiudi la chiamata di verifica del server
fetch()in un blocco try/catch:- Mostra un messaggio di errore per consentire all'utente di capire cosa non ha funzionato.
- Chiama
signalUnknownCredential()SOLO quando il server risponde esplicitamente con lo stato HTTP404(credenziale non trovata) e l'utente non è autenticato. - Il parametro
credentialIdpassato asignalUnknownCredential()DEVE essere rigorosamente la stringa dell'ID credenziale con codifica Base64URL (ad es.encoded.id), NON l'oggetto ArrayBuffer non elaboratocredential.rawId.
- Codifica la risposta: codifica la credenziale
AuthenticatorAssertionResponsecon.toJSON()prima di inviarla al server per la verifica.
// 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);
Strategie di fallback
Fallback del rilevamento delle funzionalità delle passkey
Installa sempre "webauthn-polyfills" e importalo nel contesto.
Tieni presente che, se PublicKeyCredential è supportato, lo è anche PublicKeyCredential.getClientCapabilities.
import 'webauthn-polyfills';
Sincronizzazione di riserva dell'API Signal
L'API WebAuthn Signal (webauthn-signals) è un'ottimizzazione progressiva utilizzata per mantenere i gestori delle password sincronizzati con lo stato delle credenziali del server.
- Esperienza di riserva: limitata tramite
if (PublicKeyCredential.signalUnknownCredential). Se non è supportata, la sincronizzazione della verifica in background viene ignorata correttamente senza generare eccezioni del browser.
Easy JSON Serialization Fallback
Installa sempre "webauthn-polyfills" e importalo nel contesto.
Tieni presente che, se PublicKeyCredential è supportato, lo sono anche PublicKeyCredential.parseRequestOptionsFromJSON e PublicKeyCredential.prototype.toJSON.
import 'webauthn-polyfills';