Übersicht
Hier eine Übersicht über die wichtigsten Schritte bei der Passkey-Authentifizierung:

- Definieren Sie die Challenge und andere Optionen, die für die Authentifizierung mit einem Passkey erforderlich sind. Senden Sie sie an den Client, damit Sie sie an Ihren Passkey-Authentifizierungsaufruf (
navigator.credentials.getim Web) übergeben können. Nachdem der Nutzer die Passkey-Authentifizierung bestätigt hat, wird der Passkey-Authentifizierungsaufruf aufgelöst und ein Berechtigungsnachweis (PublicKeyCredential) zurückgegeben. Der Berechtigungsnachweis enthält eine Authentifizierungs-Assertion.
- Überprüfen Sie die Authentifizierungs-Assertion.
- Wenn die Authentifizierungsbestätigung gültig ist, wird der Nutzer authentifiziert.
In den folgenden Abschnitten werden die einzelnen Schritte genauer beschrieben.
Challenge erstellen
In der Praxis ist eine Challenge ein Array von zufälligen Byte, das als ArrayBuffer-Objekt dargestellt wird.
// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8
Damit die Challenge ihren Zweck erfüllt, müssen Sie Folgendes tun:
- Achten Sie darauf, dass dieselbe Challenge nie mehr als einmal verwendet wird. Bei jedem Anmeldeversuch eine neue Challenge generieren. Verwerfen Sie die Aufgabe nach jedem Anmeldeversuch, unabhängig davon, ob er erfolgreich war oder nicht. Verwerfen Sie die Challenge auch nach einer bestimmten Zeit. Akzeptieren Sie dieselbe Herausforderung in einer Antwort nie mehr als einmal.
- Sorgen Sie dafür, dass die Identitätsbestätigung kryptografisch sicher ist. Eine Challenge sollte praktisch unmöglich zu erraten sein. Wenn Sie eine kryptografisch sichere Challenge auf der Serverseite erstellen möchten, sollten Sie eine vertrauenswürdige serverseitige FIDO-Bibliothek verwenden. Wenn Sie stattdessen eigene Challenges erstellen, verwenden Sie die integrierten kryptografischen Funktionen, die in Ihrem Technologie-Stack verfügbar sind, oder suchen Sie nach Bibliotheken, die für kryptografische Anwendungsfälle entwickelt wurden. Beispiele sind iso-crypto in Node.js oder secrets in Python. Gemäß der Spezifikation muss die Challenge mindestens 16 Byte lang sein, um als sicher zu gelten.
Nachdem Sie eine Challenge erstellt haben, speichern Sie sie in der Sitzung des Nutzers, um sie später zu überprüfen.
Optionen für Anfragen von Anmeldedaten erstellen
Erstellen Sie Optionen für die Anfrage von Anmeldedaten als publicKeyCredentialRequestOptions-Objekt.
Verwenden Sie dazu Ihre serverseitige FIDO-Bibliothek. In der Regel wird eine Dienstfunktion angeboten, mit der diese Optionen für Sie erstellt werden können. SimpleWebAuthn bietet beispielsweise generateAuthenticationOptions.
publicKeyCredentialRequestOptions sollte alle Informationen enthalten, die für die Passkey-Authentifizierung erforderlich sind. Übergeben Sie diese Informationen an die Funktion in Ihrer serverseitigen FIDO-Bibliothek, die für das Erstellen des publicKeyCredentialRequestOptions-Objekts verantwortlich ist.
Einige Felder von publicKeyCredentialRequestOptions können Konstanten sein. Andere sollten dynamisch auf dem Server definiert werden:
rpId: Die RP-ID, mit der die Anmeldedaten verknüpft werden sollen, z. B.example.com. Die Authentifizierung ist nur erfolgreich, wenn die hier angegebene RP-ID mit der RP-ID übereinstimmt, die mit den Anmeldedaten verknüpft ist. Verwenden Sie für die RP-ID denselben Wert, den Sie bei der Registrierung der Anmeldedaten inpublicKeyCredentialCreationOptionsfestgelegt haben.challenge: Ein Datenelement, das der Passkey-Anbieter signiert, um zu beweisen, dass der Nutzer zum Zeitpunkt der Authentifizierungsanfrage im Besitz des Passkeys ist. Weitere Informationen finden Sie unter Challenge erstellen.allowCredentials: Ein Array mit zulässigen Anmeldedaten für diese Authentifizierung. Übergeben Sie ein leeres Array, damit der Nutzer einen verfügbaren Passkey aus einer vom Browser angezeigten Liste auswählen kann. Weitere Informationen finden Sie unter Challenge vom RP-Server abrufen und Auffindbare Anmeldedaten im Detail.userVerification: Gibt an, ob die Nutzerbestätigung über die Displaysperre des Geräts „erforderlich“, „bevorzugt“ oder „nicht empfohlen“ ist. Lesen Sie den Abschnitt Challenge vom RP-Server abrufen.timeout: Gibt an, wie lange (in Millisekunden) der Nutzer Zeit hat, die Authentifizierung abzuschließen. Er sollte angemessen großzügig und kürzer als die Lebensdauer deschallengesein. Der empfohlene Standardwert ist 5 Minuten. Sie können ihn aber auf bis zu 10 Minuten erhöhen, was immer noch im empfohlenen Bereich liegt. Lange Zeitlimits sind sinnvoll, wenn Sie erwarten, dass Nutzer den Hybrid-Workflow verwenden, der in der Regel etwas länger dauert. Wenn das Zeitlimit für den Vorgang überschritten wird, wird einNotAllowedErrorausgegeben.
Senden Sie publicKeyCredentialRequestOptions nach der Erstellung an den Kunden.
challenge-Decodierung erfolgt clientseitig.Beispielcode: Optionen für die Anfrage zur Erstellung von Anmeldedaten erstellen
In unseren Beispielen verwenden wir die SimpleWebAuthn-Bibliothek. Hier übergeben wir die Erstellung der Optionen für die Anmeldedatenanfrage an die Funktion generateAuthenticationOptions.
import {
generateAuthenticationOptions,
} from '@simplewebauthn/server';
router.post('/signinRequest', csrfCheck, async (req, res) => {
// Ensure you nest 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 {
// Use the generateAuthenticationOptions function from SimpleWebAuthn
const options = await generateAuthenticationOptions({
rpID: process.env.HOSTNAME,
allowCredentials: [],
});
// Save the challenge in the user session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Nutzer bestätigen und anmelden
Wenn navigator.credentials.get auf dem Client erfolgreich aufgelöst wird, wird ein PublicKeyCredential-Objekt zurückgegeben.
navigator.credentials.get gibt einen PublicKeyCredential zurück.response ist ein AuthenticatorAssertionResponse. Sie stellt die Antwort des Passkey-Anbieters auf die Anweisung des Clients dar, die erforderlichen Elemente zu erstellen, um sich mit einem Passkey bei der vertrauenden Partei zu authentifizieren. Es enthält:
response.authenticatorDataundresponse.clientDataJSON, z. B. bei der Passkey-Registrierung.response.signature, der eine Signatur für diese Werte enthält.
Senden Sie das PublicKeyCredential-Objekt an den Server.
Führen Sie auf dem Server die folgenden Schritte aus:
- Informationen erfassen, die Sie zum Bestätigen der Assertion und zum Authentifizieren des Nutzers benötigen:
- Rufen Sie die erwartete Challenge ab, die Sie in der Sitzung gespeichert haben, als Sie die Authentifizierungsoptionen generiert haben.
- Rufen Sie den erwarteten Ursprung und die RP-ID ab.
- Suchen Sie in Ihrer Datenbank nach dem Nutzer. Bei auffindbaren Anmeldedaten wissen Sie nicht, wer die Authentifizierungsanfrage stellt. Dafür gibt es zwei Möglichkeiten:
- Option 1: Verwenden Sie
response.userHandleim ObjektPublicKeyCredential. Suchen Sie in der Tabelle Nutzer nach dempasskey_user_id, der mituserHandleübereinstimmt. - Option 2: Verwenden Sie die im Objekt
PublicKeyCredentialvorhandenen Anmeldedatenid. Suchen Sie in der Tabelle Anmeldedaten für öffentliche Schlüssel nach den Anmeldedatenid, die mit den Anmeldedatenidim ObjektPublicKeyCredentialübereinstimmen. Suchen Sie dann mithilfe des Fremdschlüsselspasskey_user_idin der Tabelle Users nach dem entsprechenden Nutzer.
- Option 1: Verwenden Sie
- Suchen Sie in Ihrer Datenbank nach den Informationen zu den Anmeldedaten für den öffentlichen Schlüssel, die mit der erhaltenen Authentifizierungsbestätigung übereinstimmen. Suchen Sie dazu in der Tabelle Anmeldedaten mit öffentlichem Schlüssel nach den Anmeldedaten
id, die mit den Anmeldedatenidim ObjektPublicKeyCredentialübereinstimmen.
Überprüfen Sie die Authentifizierungs-Assertion. Übergeben Sie diesen Bestätigungsschritt an Ihre serverseitige FIDO-Bibliothek, die in der Regel eine Dienstfunktion für diesen Zweck bietet. SimpleWebAuthn bietet beispielsweise
verifyAuthenticationResponse. Weitere Informationen dazu, was im Hintergrund passiert, finden Sie im Anhang: Überprüfung der Authentifizierungsantwort.Löschen Sie die Challenge, unabhängig davon, ob die Bestätigung erfolgreich war oder nicht, um Replay-Angriffe zu verhindern.
Melden Sie den Nutzer an. Wenn die Überprüfung erfolgreich war, aktualisieren Sie die Sitzungsinformationen, um den Nutzer als angemeldet zu kennzeichnen. Möglicherweise möchten Sie auch ein
user-Objekt an den Client zurückgeben, damit das Frontend Informationen verwenden kann, die mit dem neu angemeldeten Nutzer verknüpft sind.
Beispielcode: Nutzer bestätigen und anmelden
In unseren Beispielen verwenden wir die SimpleWebAuthn-Bibliothek. Hier übergeben wir die Überprüfung der Authentifizierungsantwort an die Funktion verifyAuthenticationResponse.
import {
verifyAuthenticationResponse,
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/signinResponse', csrfCheck, async (req, res) => {
const response = req.body;
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
// 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 {
// Find the credential stored to the database by the credential ID
const cred = Credentials.findById(response.id);
if (!cred) {
throw new Error('Credential not found.');
}
// Find the user - Here alternatively we could look up the user directly
// in the Users table via userHandle
const user = Users.findByPasskeyUserId(cred.passkey_user_id);
if (!user) {
throw new Error('User not found.');
}
// Prepare credential information for verification
const credential = {
id: cred.id,
publicKey: isoBase64URL.toBuffer(cred.publicKey),
transports: cred.transports,
};
// Verify the credential
const { verified, authenticationInfo } = await verifyAuthenticationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
credential,
requireUserVerification: false,
});
if (!verified) {
throw new Error('User verification failed.');
}
// Kill the challenge for this session.
delete req.session.challenge;
req.session.username = user.username;
req.session['signed-in'] = 'yes';
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Anhang: Bestätigung der Authentifizierungsantwort
Die Überprüfung der Authentifizierungsantwort umfasst die folgenden Schritte:
- Achten Sie darauf, dass die RP-ID mit Ihrer Website übereinstimmt.
- Achten Sie darauf, dass der Ursprung der Anfrage mit dem Anmeldeursprung Ihrer Website übereinstimmt. Informationen zu Android-Apps finden Sie unter Ursprung bestätigen.
- Prüfen Sie, ob das Gerät die von Ihnen gestellte Challenge bereitstellen konnte.
- Prüfen Sie, ob der Nutzer bei der Authentifizierung die von Ihnen als RP festgelegten Anforderungen erfüllt hat. Wenn Sie eine Nutzerbestätigung benötigen, muss das Flag
uv(Nutzer bestätigt) inauthenticatorDataauftruegesetzt sein. Prüfen Sie, ob das Flagup(Nutzer anwesend) inauthenticatorDataauftruegesetzt ist, da für Passkeys immer die Anwesenheit des Nutzers erforderlich ist. - Signatur überprüfen Zum Bestätigen der Signatur benötigen Sie Folgendes:
- Die Signatur, d. h. die signierte Aufgabe:
response.signature - Der öffentliche Schlüssel, mit dem die Signatur verifiziert werden soll.
- Die ursprünglich signierten Daten. Dies sind die Daten, deren Signatur überprüft werden soll.
- Der kryptografische Algorithmus, der zum Erstellen der Signatur verwendet wurde.
- Die Signatur, d. h. die signierte Aufgabe:
Weitere Informationen zu diesen Schritten finden Sie im Quellcode für verifyAuthenticationResponse von SimpleWebAuthn oder in der vollständigen Liste der Überprüfungen in der Spezifikation.