Ce guide explique comment implémenter l'authentification des utilisateurs connus à l'aide d'identifiants détectables, à la fois via des déclencheurs de boutons explicites et des suggestions de saisie automatique du navigateur (UI conditionnelle).
Côté serveur
Génération d'options
Créez un point de terminaison qui génère des paramètres de requête WebAuthn à l'aide d'une bibliothèque validée conformément aux normes.
- Utiliser l'ID RP prédéfini : utilisez l'ID RP approprié prédéfini comme chaîne constante.
- Générer un test : générez un tampon aléatoire à entropie élevée et sécurisé de manière cryptographique, stockez-le de manière sécurisée dans la session de l'utilisateur et encodez-le au format Base64URL.
- Mappage des identifiants détectables : spécifiez un tableau vide
[]pourallowCredentials. Cela demande des identifiants détectables, ce qui signifie que l'utilisateur n'a pas besoin de saisir son nom d'utilisateur en premier. Le fournisseur de clés d'accès présentera les comptes disponibles. - Niveau de validation de l'utilisateur : définissez
userVerification: "preferred"(ou"required"si les règles de conformité de l'entreprise l'exigent explicitement).- Le niveau de contrainte
userVerificationdemandé DOIT être conservé dans l'enregistrement de session du serveur au niveau du point de terminaison des options, plutôt que d'être renvoyé par le client via des chaînes de requête. Cela permet au point de terminaison de validation d'appliquer des contraintes de correspondance strictes de manière sécurisée, sans risque de manipulation du client.
- Le niveau de contrainte
// 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";
Point de terminaison de validation
Vérifiez de manière sécurisée l'assertion renvoyée par le client pour authentifier l'utilisateur :
- Valider le test de session : appliquez une correspondance stricte entre la réponse du client et le test attendu stocké dans la session.
- Appliquer les préférences de vérification de l'utilisateur :
- Autorisez les authentificateurs sans vérification de l'utilisateur (par exemple, les verrouillages d'écran de l'authentificateur désactivés) si la session
expectedUserVerificationa demandé"preferred", en transmettantrequireUserVerification: falseà votre bibliothèque de validation côté serveur. Si vous y êtes invité"required", appliquez strictement la saisie des données biométriques/du code.
- Autorisez les authentificateurs sans vérification de l'utilisateur (par exemple, les verrouillages d'écran de l'authentificateur désactivés) si la session
- Erreur du serveur 404 propre : si l'ID d'identifiant renvoyé par le client est introuvable dans la base de données, renvoyez une erreur HTTP
404explicite afin que le client puisse déclencher l'API Signal.
Logique côté client
Annotation de formulaire HTML
Annotez vos entrées de nom d'utilisateur et de mot de passe pour exploiter l'UI conditionnelle de manière native. Les jetons de saisie semi-automatique combinent les paramètres de la spécification WebAuthn, et la mise au point automatique déclenche immédiatement le pop-up de saisie automatique du navigateur lorsque l'entrée est sélectionnée.
<!-- 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>
Flux de boutons explicite
Déclenchez l'authentification par clé d'accès lorsqu'un utilisateur clique sur un bouton "Se connecter avec une clé d'accès". Annulez tous les appels de saisie automatique de formulaire (Conditional Get) en cours avant d'appeler l'invite de clé d'accès.
Flux de médiation conditionnelle (saisie automatique de formulaire)
Activez les suggestions de saisie automatique de formulaire au chargement de la page pour proposer l'authentification par clé d'accès de manière native lorsque les utilisateurs sélectionnent les champs de connexion :
- Détection des fonctionnalités : appelez
PublicKeyCredential.getClientCapabilities()au chargement de page et ignorez la connexion avec une clé d'accès siconditionalGetn'est pas disponible. - Options de décodage : décodez l'objet JSON d'identifiant récupéré avec
PublicKeyCredential.parseRequestOptionsFromJSON(). - Appeler une requête GET conditionnelle : appelez
navigator.credentials.get()avecmediation: "conditional"et transmettez un signalAbortController. Cela enregistre la saisie automatique de manière silencieuse sans afficher de boîte de dialogue de clé d'accès. - Ségrégation des exceptions Try/Catch : encapsulez l'appel
navigator.credentials.getdans un bloc try/catch :NotAllowedError: l'utilisateur a annulé l'invite de connexion par clé d'accès ou le délai d'expiration a été dépassé.AbortError: la requête d'authentification a été annulée de manière programmatique.
- API Call Signal : encapsulez l'appel de vérification du serveur
fetch()dans un bloc try/catch :- Affichez un message d'erreur pour que l'utilisateur comprenne ce qui s'est passé.
- N'appelez
signalUnknownCredential()QUE lorsque le serveur répond explicitement avec l'état HTTP404(Identifiant introuvable) et que l'utilisateur n'est pas authentifié. - Le paramètre
credentialIdtransmis àsignalUnknownCredential()DOIT être strictement la chaîne d'ID d'identifiant encodée en Base64URL (par exemple,encoded.id), et NON l'objet ArrayBuffer brutcredential.rawId.
- Encoder la réponse : encodez l'identifiant
AuthenticatorAssertionResponseavec.toJSON()avant de l'envoyer au serveur pour validation.
// 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);
Stratégies de remplacement
Solution de secours pour la détection des fonctionnalités de clé d'accès
Installez toujours "webauthn-polyfills" et importez-le dans le contexte.
Tant que PublicKeyCredential est compatible, PublicKeyCredential.getClientCapabilities l'est également.
import 'webauthn-polyfills';
Solution de secours pour la synchronisation de l'API Signal
L'API WebAuthn Signal (webauthn-signals) est une optimisation progressive utilisée pour synchroniser les gestionnaires de mots de passe avec l'état des identifiants du serveur.
- Expérience de remplacement : accès limité via
if (PublicKeyCredential.signalUnknownCredential). Si elle n'est pas compatible, la synchronisation de la validation en arrière-plan est ignorée sans générer d'exceptions de navigateur.
Easy JSON Serialization Fallback
Installez toujours "webauthn-polyfills" et importez-le dans le contexte.
Tant que PublicKeyCredential est compatible, PublicKeyCredential.parseRequestOptionsFromJSON et PublicKeyCredential.prototype.toJSON le sont également.
import 'webauthn-polyfills';