Übersicht
Hier ist eine allgemeine Übersicht über die wichtigsten Schritte bei der Passkey-Authentifizierung:
- Lege die Identitätsbestätigung und andere Optionen fest, die für die Authentifizierung mit einem Passkey erforderlich sind. Senden Sie sie an den Client, damit Sie sie an Ihren Passkey-Authentifizierungsanruf übergeben können (
navigator.credentials.get
im Web). Nachdem der Nutzer die Passkey-Authentifizierung bestätigt hat, wird der Passkey-Authentifizierungsanruf aufgelöst und ein Ausweisdokument (PublicKeyCredential
) wird zurückgegeben. Die Anmeldedaten enthalten eine Authentifizierungs-Assertion.
- Prüfen Sie die Assertion für die Authentifizierung.
- Wenn die Authentifizierungs-Assertion gültig ist, authentifizieren Sie den Nutzer.
In den folgenden Abschnitten werden die einzelnen Schritte ausführlich erläutert.
<ph type="x-smartling-placeholder">Wettkampf erstellen
In der Praxis ist eine Aufgabe ein Array von zufälligen Byte, die als ArrayBuffer
-Objekt dargestellt werden.
// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8
Damit die Challenge ihren Zweck erfüllt, musst du:
- Sorge dafür, dass dieselbe Aufgabe nie mehr als einmal verwendet wird. Bei jedem Anmeldeversuch eine neue Herausforderung generieren. Verwerfen Sie die Identitätsbestätigung nach jedem Anmeldeversuch, unabhängig davon, ob er erfolgreich war oder fehlgeschlagen ist. Den Wettkampf nach einer bestimmten Dauer auch verwerfen. Nehmen Sie die gleiche Aufgabe nicht mehrmals in einer Antwort an.
- Achten Sie darauf, dass die Identitätsbestätigung kryptografisch sicher ist. Eine Herausforderung sollte praktisch unmöglich zu erraten sein. Um eine serverseitige kryptografisch sichere Identitätsbestätigung zu erstellen, sollten Sie sich auf eine serverseitige FIDO-Bibliothek verlassen, der Sie vertrauen. Wenn Sie stattdessen eigene Herausforderungen erstellen, können Sie die integrierten kryptografischen Funktionen Ihres Technologie-Stacks verwenden oder nach Bibliotheken suchen, die für kryptografische Anwendungsfälle entwickelt wurden. Beispiele hierfür 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 einen Wettkampf erstellt haben, speichern Sie ihn in der Nutzersitzung, um ihn später zu bestätigen.
Optionen für Anmeldedatenanfragen erstellen
Erstellen Sie Optionen für Anmeldedatenanfragen als publicKeyCredentialRequestOptions
-Objekt.
Nutzen Sie dazu Ihre serverseitige FIDO-Bibliothek. Normalerweise wird eine Dienstfunktion zur Verfügung gestellt, die diese Optionen für Sie erstellen kann. SimpleWebAuthn bietet zum Beispiel 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 die Erstellung des publicKeyCredentialRequestOptions
-Objekts zuständig ist.
Einige von publicKeyCredentialRequestOptions
Felder 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 mit dem Berechtigungsnachweis verknüpften RP-ID übereinstimmt. Verwende zum Ausfüllen der RP-ID denselben Wert wie die RP-ID, die du bei der Registrierung der Anmeldedaten inpublicKeyCredentialCreationOptions
festgelegt hast.challenge
: Ein Datenelement, das der Passkey-Anbieter signiert, um nachzuweisen, dass der Nutzer den Passkey zum Zeitpunkt der Authentifizierungsanfrage besitzt. Weitere Informationen findest du unter Herausforderung erstellen.allowCredentials
: ein Array zulässiger 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 Herausforderung vom RP-Server abrufen und Detaillierte Informationen zu den sichtbaren Anmeldedaten.userVerification
: Gibt an, ob die Nutzerbestätigung über die Displaysperre des Geräts „erforderlich“ oder „bevorzugt“ ist oder „entmutigt“. Weitere Informationen finden Sie unter Herausforderung vom RP-Server abrufen.timeout
: Gibt an, wie lange (in Millisekunden) der Nutzer für die Authentifizierung benötigt. Es sollte relativ großzügig sein und kürzer als die Lebensdauer vonchallenge
sein. Der empfohlene Standardwert ist 5 Minuten, aber Sie können ihn erhöhen – auf bis zu 10 Minuten, was noch innerhalb des empfohlenen Bereichs liegt. Lange Zeitlimits sind sinnvoll, wenn Sie davon ausgehen, dass Nutzer den hybriden Workflow verwenden, der normalerweise etwas länger dauert. Wenn beim Vorgang eine Zeitüberschreitung auftritt, wird einNotAllowedError
ausgelöst.
Nachdem Sie publicKeyCredentialRequestOptions
erstellt haben, senden Sie es an den Client.
![publicKeyCredentialCreationOptions vom Server gesendet](https://developers.google.cn/identity/passkeys/developer-guides/server-authentication/images/auth-options.png?authuser=8&hl=de)
challenge
-Decodierung erfolgt clientseitig.Beispielcode: Optionen zum Erstellen von Anmeldedatenanfragen
Wir verwenden in unseren Beispielen die SimpleWebAuthn-Bibliothek. Hier übergeben wir die Erstellung von Optionen für Anmeldedatenanfragen an die zugehörige generateAuthenticationOptions
-Funktion.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} 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.
![Vom Server gesendetes PublicKeyCredential-Objekt](https://developers.google.cn/identity/passkeys/developer-guides/server-authentication/images/publicKeyCredential.png?authuser=8&hl=de)
navigator.credentials.get
gibt eine PublicKeyCredential
zurück.response
ist ein AuthenticatorAssertionResponse
. Er stellt die Antwort des Passkey-Anbieters auf die Anweisung des Clients dar, das zu erstellen, was für die Authentifizierung mit einem Passkey auf dem RP erforderlich ist. Es enthält:
response.authenticatorData
undresponse.clientDataJSON
, z. B. bei der Passkey-Registrierung.response.signature
, die eine Signatur über diese Werte enthält.
Senden Sie das Objekt PublicKeyCredential
an den Server.
Führen Sie auf dem Server folgende Schritte aus:
<ph type="x-smartling-placeholder">![Datenbankschema](https://developers.google.cn/identity/passkeys/developer-guides/server-authentication/images/database-schema-passkeys.png?authuser=8&hl=de)
- Sammeln Sie die Informationen, die Sie benötigen, um die Assertion zu prüfen und den Nutzer zu authentifizieren:
<ph type="x-smartling-placeholder">
- </ph>
- Rufen Sie die erwartete Identitätsbestätigung ab, die Sie in der Sitzung gespeichert haben, als Sie die Authentifizierungsoptionen generiert haben.
- Ruft den erwarteten origin-Wert und die RP-ID ab.
- Finden Sie in Ihrer Datenbank heraus, wer der Nutzer ist. Bei auffindbaren Anmeldedaten wissen Sie nicht, wer der Nutzer ist, der die Authentifizierungsanfrage stellt. Sie haben zwei Möglichkeiten, dies herauszufinden:
<ph type="x-smartling-placeholder">
- </ph>
- Option 1:
response.userHandle
imPublicKeyCredential
-Objekt verwenden Suchen Sie in der Tabelle Nutzer nach derpasskey_user_id
, dieuserHandle
entspricht. - Option 2: Sie verwenden die Anmeldedaten
id
, die im ObjektPublicKeyCredential
vorhanden sind. Suchen Sie in der Tabelle Anmeldedaten mit öffentlichem Schlüssel nach den Anmeldedatenid
, die mit den Anmeldedatenid
im ObjektPublicKeyCredential
übereinstimmen. Suchen Sie dann mithilfe des Fremdschlüsselspasskey_user_id
in der Tabelle Users nach dem entsprechenden Nutzer.
- Option 1:
- Suchen Sie in Ihrer Datenbank nach den Anmeldedaten für den öffentlichen Schlüssel, die mit der Authentifizierungs-Assertion übereinstimmen, die Sie erhalten haben. Suchen Sie dazu in der Tabelle Anmeldedaten für öffentlichen Schlüssel nach den Anmeldedaten
id
, die mit den Anmeldedatenid
im ObjektPublicKeyCredential
übereinstimmen.
Bestätigen Sie die Authentifizierungs-Assertion. Übergeben Sie diesen Bestätigungsschritt an Ihre serverseitige FIDO-Bibliothek, die in der Regel eine Dienstprogrammfunktion für diesen Zweck bereitstellt. SimpleWebAuthn bietet zum Beispiel
verifyAuthenticationResponse
. Weitere Informationen dazu finden Sie im Anhang: Überprüfung der Authentifizierungsantwort.Lösche die Identitätsbestätigung, unabhängig davon, ob die Bestätigung erfolgreich ist oder nicht, um Replay-Angriffe zu verhindern.
Melden Sie sich an. Wenn die Bestätigung 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 Front-End Informationen verwenden kann, die dem neu angemeldeten Nutzer zugeordnet sind.
Beispielcode: Nutzer bestätigen und anmelden
Wir verwenden in unseren Beispielen die SimpleWebAuthn-Bibliothek. Hier übergeben wir die Bestätigung der Authentifizierungsantwort an die Funktion verifyAuthenticationResponse
.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
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.');
}
// Base64URL decode some values
const authenticator = {
credentialPublicKey: isoBase64URL.toBuffer(cred.publicKey),
credentialID: isoBase64URL.toBuffer(cred.id),
transports: cred.transports,
};
// Verify the credential
const { verified, authenticationInfo } = await verifyAuthenticationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
authenticator,
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: Überprüfung der Authentifizierungsantwort
Die Überprüfung der Authentifizierungsantwort besteht aus den folgenden Prüfungen:
- Achten Sie darauf, dass die RP-ID mit Ihrer Website übereinstimmt.
- Prüfen Sie, ob der Ursprung der Anfrage mit der Anmeldequelle Ihrer Website übereinstimmt. Lesen Sie für Android-Apps den Artikel Ursprung überprüfen.
- Prüfen Sie, ob das Gerät die von Ihnen vorgegebene Aufgabe erfüllen konnte.
- Prüfen Sie, ob der Nutzer bei der Authentifizierung die Anforderungen erfüllt hat, die Sie als RP festlegen. Wenn eine Nutzerbestätigung erforderlich ist, muss
uv
(vom Nutzer verifiziert) inauthenticatorData
den Werttrue
haben. Das Flagup
(Nutzer anwesend) inauthenticatorData
muss auftrue
gesetzt sein, da die Anwesenheit des Nutzers für Passkeys immer erforderlich ist. - Bestätigen Sie die Signatur. Zum Bestätigen der Signatur benötigen Sie Folgendes:
<ph type="x-smartling-placeholder">
- </ph>
- Die Unterschrift, d. h. die signierte Aufgabe:
response.signature
- Der öffentliche Schlüssel, mit dem die Signatur verifiziert werden soll.
- Die ursprünglichen signierten Daten. Dies sind die Daten, deren Signatur bestätigt werden soll.
- Der kryptografische Algorithmus, mit dem die Signatur erstellt wurde.
- Die Unterschrift, d. h. die signierte Aufgabe:
Weitere Informationen zu diesen Schritten finden Sie im Quellcode von SimpleWebAuthn für verifyAuthenticationResponse
oder in der vollständigen Liste der Überprüfungen in der Spezifikation.