In diesem Leitfaden wird beschrieben, wie Sie Nutzern die Möglichkeit geben, einen Passkey für ihr Konto zu registrieren. So erhalten sie eine hochsichere, Phishing-resistente Alternative zur passwortlosen Anmeldung.
Datenbankanforderungen
Damit Passkey-Registrierungen unterstützt werden, müssen in der Tabelle mit den Datenbank-Anmeldedaten die folgenden Felder gespeichert werden:
export interface StoredPasskeyCredential {
id: string; // Base64URL-encoded credential ID (unique lookup key)
passkeyUserId: string; // Associated application user ID
credentialPublicKey: string; // Base64URL-encoded public key used to verify assertion signatures
credentialType: "public-key";
credentialDeviceType: "singleDevice" | "multiDevice"; // Helps distinguish device-bound vs cloud-synced passkeys
credentialBackedUp: boolean; // Boolean backup state reported by the authenticator
aaguid: string; // Authenticator Attestation GUID
providerIcon?: string; // Provider icon derived from the AAGUID registry (dark or light theme URLs)
name: string; // Provider name derived from AAGUID registry
transports: string[]; // Array of transport names (e.g. 'internal', 'hybrid') necessary for exclusion options
lastUsedAt?: number; // Optional epoch timestamp of last sign-in
registeredAt: number; // Registration epoch timestamp
counter: number; // Authenticator sign-in signature counter used to prevent replay attacks
}
Serverseitig
Optionen generieren
Erstellen Sie einen Endpunkt, der WebAuthn-Erstellungsparameter generiert. Verlassen Sie sich auf eine geprüfte Bibliothek gemäß den Kategoriestandards, anstatt die Kryptografie selbst zu entwickeln.
- Vordefinierte RP-ID verwenden: Verwenden Sie die vordefinierte korrekte RP-ID als konstanten String.
- Sichere Challenge erstellen: Generieren Sie auf dem Server einen kryptografisch sicheren Zufallspuffer mit hoher Entropie, speichern Sie ihn sicher in der Nutzersitzung und codieren Sie ihn als Base64URL für die Bereitstellung der Optionen.
- Doppelte Passkeys vermeiden: Ordnen Sie die vorhandenen vorregistrierten Anmeldedaten-IDs des Nutzers dem Array
excludeCredentialszu. So wird verhindert, dass der Authenticator doppelte Anmeldedaten für dasselbe Passkey-Anbieterkonto registriert. - Auffindbare Anmeldedaten erzwingen: Legen Sie
requireResidentKey: trueundresidentKey: "required"in denauthenticatorSelection-Optionen fest, um auffindbare Anmeldedaten anzufordern, die für die auffindbare Anmeldung erforderlich sind. - Nutzerbestätigung konfigurieren: Geben Sie
userVerification: "preferred"oderuserVerification: "required"an. Viele Compliance-Anwendungsfälle (z.B. im Finanz- oder Gesundheitswesen) erfordern, dass'required'die Nutzerbestätigung bei der Erstellung erzwingt. - Anhangsumfang festlegen:
- Promotion Flow: Wenn Sie die Erstellung eines Passkeys direkt nach der Anmeldung mit einem Standardpasswort oder nach der Registrierung vorschlagen, setzen Sie
authenticatorAttachment: "platform", um den Plattform-Authenticator zu erzwingen und Aufforderungen für externe Sicherheitsschlüssel zu umgehen. - Verwaltungsablauf: Wenn der Aufruf über einen speziellen Bereich für Einstellungen oder Sicherheit erfolgt, in dem neben dem Plattform-Authenticator auch externe Sicherheitsschlüssel unterstützt werden, lassen Sie das Attribut
authenticatorAttachmentvollständig weg. - Tipp: Akzeptieren Sie ein
promotion: boolean-Anfrage-Flag, um beide Abläufe mit einem einzigen Endpunkt bedingt zu verarbeiten.
- Promotion Flow: Wenn Sie die Erstellung eines Passkeys direkt nach der Anmeldung mit einem Standardpasswort oder nach der Registrierung vorschlagen, setzen Sie
// Options generation example
const options = {
challenge: serverGeneratedBase64UrlChallenge, // Cryptographically random challenge
rp: { id: "example.com", name: "Secure Application" },
user: {
id: userBase64UrlId, // Unique base64url string identifying the account
name: "user@example.com",
displayName: "Jane Doe",
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7,
},
{
type: "public-key",
alg: -257,
},
],
excludeCredentials: userExistingCredentials.map((cred) => ({
type: "public-key",
id: cred.id,
transports: cred.transports,
})),
authenticatorSelection: {
residentKey: "required",
requireResidentKey: true,
userVerification: "preferred",
...(isPromotionFlow && { authenticatorAttachment: "platform" }),
},
};
Bestätigung
- Challenge-Überprüfung: Die Challenge wird sicher mit der erwarteten sitzungsgebundenen Challenge abgeglichen.
- Anwesenheit des Nutzers prüfen:
- Achten Sie darauf, dass das Flag „User Present“ (UP), das in den geparsten Authentifikatordaten zurückgegeben wird,
trueist, um die physische Anwesenheit des Nutzers zum Zeitpunkt der Erstellung zu bestätigen.
- Achten Sie darauf, dass das Flag „User Present“ (UP), das in den geparsten Authentifikatordaten zurückgegeben wird,
- Lockerung der Überprüfung für „preferred“:
- Wenn in den Erstellungsoptionen
userVerification: "preferred"angegeben ist, MUSS der Aufruf für die serverseitige Überprüfung mitrequireUserVerification: falsekonfiguriert werden. Andernfalls lösen Authentifikatoren, die sich ohne Nutzerbestätigung registrieren (z.B. bei deaktivierter Displaysperre), fälschlicherweise Fehler bei der Serverbestätigung aus.
- Wenn in den Erstellungsoptionen
Clientseitige Logik
- UI beim Seitenaufbau sperren:
- Rufen Sie beim Laden der Seite
PublicKeyCredential.getClientCapabilities()auf und deaktivieren Sie die Schaltfläche „Passkey erstellen“, wennconditionalGetoderpasskeyPlatformAuthenticatornicht verfügbar ist.
- Rufen Sie beim Laden der Seite
- Erstellung aufrufen und serialisieren: Decodieren Sie die Serveroptionen mit
PublicKeyCredential.parseCreationOptionsFromJSON()und übergeben Sie die resultierende Konfiguration annavigator.credentials.create().- Rufe
credential.toJSON()auf, um dieAuthenticatorAttestationResponsein ein gültiges, JSON-serialisierbares Objekt zu codieren, bevor du den Bestätigungsendpunkt abrufst.
- Rufe
- WebAuthn-Ausnahmen verarbeiten:
InvalidStateError: Ein übereinstimmender Passkey ist bereits vorhanden (Übereinstimmung durchexcludeCredentials).NotAllowedError: Der Nutzer hat den Passkey-Authentifizierungsdialog abgebrochen oder es ist eine Zeitüberschreitung aufgetreten.AbortError: Der Vorgang wurde abgebrochen.SecurityError: Fehler aufgrund von sicheren Ursprüngen (HTTPS) oder nicht übereinstimmenden RP-IDs (Konfigurationsprobleme).
- Try/Catch-Trennung für die Signal API:
- Fügen Sie den Aufruf der Serverbestätigung
fetch()in einen Try/Catch-Block ein. Rufen SiesignalUnknownCredential()auf, wenn der Abruf der Serverbestätigung fehlschlägt (beliebiger Statusresponse.ok === falseoder Netzwerkfehler).
- Fügen Sie den Aufruf der Serverbestätigung
// optionsFetch and registerVerifyFetch are app-defined HTTP methods
import { optionsFetch, registerVerifyFetch } from "./api.js";
async function registerPasskey(isPromotion = false) {
// Verify passkey capability and conditional UI are available
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (
!capabilities.passkeyPlatformAuthenticator ||
!capabilities.conditionalGet
) {
// Hide "Create passkey" buttons and fall back to password flows instead
showStandardPasswordFallbackUI();
return;
}
const creationOptionsJSON = await optionsFetch({ promotion: isPromotion });
const publicKey =
PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);
let credential;
try {
// passkey prompt execution
credential = await navigator.credentials.create({ publicKey });
} catch (err) {
if (err.name === "InvalidStateError") {
console.log("A passkey already exists for this account.");
alert("A passkey already exists for this account.");
} else if (err.name === "SecurityError") {
console.error("Configuration RP ID or Secure Context error.");
alert("Configuration RP ID or Secure Context error.");
} else if (err.name === "NotAllowedError") {
console.log("User cancelled the passkey dialog.");
} else if (err.name === "AbortError") {
console.log("The creation operation has been aborted.");
}
return; // Safe API exit, do not signal unknown for standard WebAuthn cancels
}
// Server Verification phase (Segregated Try/Catch)
let encodedResponse = credential.toJSON();
try {
const response = await registerVerifyFetch(encodedResponse);
if (!response.ok) {
// Server verification failed to verify/authenticate the credential (orphaned)
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encodedResponse.id, // Base64URL-encoded credential ID
});
}
} catch (serverErr) {
console.error("Server verification network failure:", serverErr);
await publickeycredential.signalunknowncredential({
rpId, // RP ID must match the one defined on the server
credentialid: encodedresponse.id, // base64url-encoded credential id
});
}
}
Fallback-Strategien
Signal API-Synchronisierungs-Fallback
Die WebAuthn Signal API (webauthn-signals) ist eine progressive Optimierung, mit der Passwortmanager mit dem Status der Serveranmeldedaten synchronisiert werden.
- Fallback-Verfahren: Wenn
PublicKeyCredential.signalUnknownCredentialvom Browser nicht unterstützt wird, MUSS der Aufruf sicher über die Feature-Erkennung (if (PublicKeyCredential.signalUnknownCredential)) umgangen werden. Bei der serverseitigen Überprüfung wird der Fehler dann einfach protokolliert, ohne dass Manager-Updates ausgelöst werden.
Einfacher Fallback für die JSON-Serialisierung
Installieren Sie immer „webauthn-polyfills“ und importieren Sie es in den Kontext.
Solange PublicKeyCredential unterstützt wird, werden auch PublicKeyCredential.parseCreationOptionsFromJSON und PublicKeyCredential.prototype.toJSON unterstützt.
import 'webauthn-polyfills';