Überblick
Hier finden Sie eine allgemeine Übersicht über die wichtigsten Schritte bei der Passkey-Registrierung:
- Legen Sie Optionen zum Erstellen eines Passkeys fest. Senden Sie sie an den Client, damit Sie sie an den Aufruf zur Passkey-Erstellung übergeben können: den WebAuthn API-Aufruf
navigator.credentials.create
im Web undcredentialManager.createCredential
unter Android. Nachdem der Nutzer die Erstellung des Passkeys bestätigt hat, wird der Aufruf zur Passkey-Erstellung aufgelöst und gibt ein AusweisdokumentPublicKeyCredential
zurück. - Überprüfen Sie die Anmeldedaten und speichern Sie sie auf dem Server.
In den folgenden Abschnitten werden die einzelnen Schritte ausführlich beschrieben.
Optionen für das Erstellen von Anmeldedaten erstellen
Als Erstes müssen Sie auf dem Server ein PublicKeyCredentialCreationOptions
-Objekt erstellen.
Nutzen Sie dazu Ihre serverseitige FIDO-Bibliothek. In der Regel bietet er eine Dienstfunktion, mit der diese Optionen für Sie erstellt werden können. SimpleWebAuthn bietet z. B. generateRegistrationOptions
.
PublicKeyCredentialCreationOptions
sollte alles enthalten, was zum Erstellen von Passkeys erforderlich ist: Informationen über den Nutzer, den RP und eine Konfiguration für die Attribute der von Ihnen erstellten Anmeldedaten. Nachdem Sie alle Elemente definiert haben, übergeben Sie sie nach Bedarf an die Funktion in Ihrer serverseitigen FIDO-Bibliothek, die für das Erstellen des PublicKeyCredentialCreationOptions
-Objekts zuständig ist.
Einige der PublicKeyCredentialCreationOptions
-Felder können Konstanten sein. Andere sollten dynamisch auf dem Server definiert werden:
rpId
: Verwenden Sie serverseitige Funktionen oder Variablen, die den Hostnamen Ihrer Webanwendung angeben, z. B.example.com
, um die RP-ID auf dem Server auszufüllen.user.name
unduser.displayName
:Verwenden Sie zum Ausfüllen dieser Felder die Sitzungsinformationen des angemeldeten Nutzers (oder die Kontoinformationen des neuen Nutzers, wenn der Nutzer bei der Registrierung einen Passkey erstellt).user.name
ist in der Regel eine E-Mail-Adresse und für die RP eindeutig.user.displayName
ist ein nutzerfreundlicher Name. Nicht alle Plattformen verwendendisplayName
.user.id
: Ein zufälliger, eindeutiger String, der bei der Kontoerstellung generiert wird. Im Gegensatz zu Nutzernamen, die bearbeitet werden können, sollte sie dauerhaft sein. Die User-ID dient der Identifizierung eines Kontos, darf jedoch keine personenidentifizierbaren Informationen enthalten. Sie haben wahrscheinlich bereits eine User-ID in Ihrem System. Erstellen Sie bei Bedarf jedoch eine spezielle für Passkeys, damit diese keine personenidentifizierbaren Informationen enthalten.excludeCredentials
: Eine Liste mit vorhandenen Anmeldedaten-IDs, um das Wiederholen eines Passkeys vom Passkey-Anbieter zu verhindern. Suchen Sie in der Datenbank nach den Anmeldedaten dieses Nutzers, um dieses Feld auszufüllen. Weitere Informationen finden Sie im Hilfeartikel Das Erstellen eines neuen Passkeys verhindern, falls bereits vorhanden.challenge
: Die Identitätsbestätigung ist nur dann relevant, wenn Sie die Attestierung verwenden, um die Identität eines Passkey-Anbieters und die von ihm gesendeten Daten zu verifizieren. Aber auch wenn Sie keine Attestierung verwenden, ist die Aufgabe ein Pflichtfeld. In diesem Fall können Sie der Einfachheit halber für diese Herausforderung ein einzelnes0
festlegen. Eine Anleitung zum Erstellen einer sicheren Authentifizierungsaufforderung finden Sie unter Serverseitige Passkey-Authentifizierung.
Codierung und Decodierung
PublicKeyCredentialCreationOptions
enthalten Felder vom Typ ArrayBuffer
und werden daher von JSON.stringify()
nicht unterstützt. Das bedeutet, dass zur Übertragung von PublicKeyCredentialCreationOptions
über HTTPS derzeit einige Felder auf dem Server mit base64URL
manuell codiert und dann auf dem Client decodiert werden müssen.
- Auf dem Server erfolgt die Codierung und Decodierung in der Regel von der serverseitigen FIDO-Bibliothek.
- Auf dem Client muss die Codierung und Decodierung derzeit manuell erfolgen. In Zukunft wird es einfacher werden: Es wird eine Methode zum Konvertieren von Optionen als JSON in
PublicKeyCredentialCreationOptions
zur Verfügung stehen. Sehen Sie sich den Status der Implementierung in Chrome an.
Beispielcode: Optionen zum Erstellen von Anmeldedaten erstellen
Wir verwenden in unseren Beispielen die SimpleWebAuthn-Bibliothek. Hier übergeben wir die Erstellung von Optionen für Anmeldedaten mit öffentlichem Schlüssel an die Funktion generateRegistrationOptions
.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/registerRequest', csrfCheck, sessionCheck, async (req, res) => {
const { user } = res.locals;
// 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 {
// `excludeCredentials` prevents users from re-registering existing
// credentials for a given passkey provider
const excludeCredentials = [];
const credentials = Credentials.findByUserId(user.id);
if (credentials.length > 0) {
for (const cred of credentials) {
excludeCredentials.push({
id: isoBase64URL.toBuffer(cred.id),
type: 'public-key',
transports: cred.transports,
});
}
}
// Generate registration options for WebAuthn create
const options = generateRegistrationOptions({
rpName: process.env.RP_NAME,
rpID: process.env.HOSTNAME,
userID: user.id,
userName: user.username,
userDisplayName: user.displayName || '',
attestationType: 'none',
excludeCredentials,
authenticatorSelection: {
authenticatorAttachment: 'platform',
requireResidentKey: true
},
});
// Keep the challenge in the session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).send({ error: e.message });
}
});
Öffentlichen Schlüssel speichern
Wenn navigator.credentials.create
auf dem Client erfolgreich aufgelöst wird, bedeutet dies, dass ein Passkey erstellt wurde. Ein PublicKeyCredential
-Objekt wird zurückgegeben.
Das PublicKeyCredential
-Objekt enthält ein AuthenticatorAttestationResponse
-Objekt, das die Antwort des Passkey-Anbieters auf die Anweisung des Clients zum Erstellen eines Passkeys darstellt. Es enthält Informationen über die neuen Anmeldedaten, die Sie als RP benötigen, um den Nutzer später zu authentifizieren. Weitere Informationen zu AuthenticatorAttestationResponse
finden Sie im Anhang: AuthenticatorAttestationResponse
.
Senden Sie das PublicKeyCredential
-Objekt an den Server. Bestätigen Sie die E-Mail, nachdem Sie sie erhalten haben.
Übergeben Sie diesen Verifizierungsschritt an Ihre serverseitige FIDO-Bibliothek. Es bietet in der Regel eine Dienstfunktion für diesen Zweck. SimpleWebAuthn bietet z. B. verifyRegistrationResponse
. Weitere Informationen finden Sie im Anhang: Bestätigung der Registrierungsantwort.
Nach erfolgreicher Verifizierung speichern Sie die Anmeldedaten in Ihrer Datenbank, damit sich der Nutzer später mit dem Passkey authentifizieren kann, der diesen Anmeldedaten zugeordnet ist.
Verwenden Sie eine dedizierte Tabelle für Anmeldedaten mit öffentlichem Schlüssel, die mit Passkeys verknüpft sind. Ein Nutzer kann nur ein einziges Passwort haben, aber mehrere Passkeys – z. B. einen Passkey, der über den Apple iCloud-Schlüsselbund synchronisiert wird, und einen, der über den Google Passwortmanager synchronisiert wird.
Hier ist ein Beispielschema, das Sie zum Speichern von Anmeldedaten verwenden können:
- Tabelle Nutzer:
user_id
: Die primäre Nutzer-ID. Eine zufällige, eindeutige, dauerhafte ID für den Nutzer. Verwenden Sie diesen Schlüssel als Primärschlüssel für die Tabelle Nutzer.username
: Ein benutzerdefinierter Nutzername, der möglicherweise bearbeitet werden kann.passkey_user_id
: Die Passkey-spezifische Nutzer-ID ohne personenidentifizierbare Informationen, dargestellt durchuser.id
in Ihren Registrierungsoptionen. Wenn der Nutzer später versucht, sich zu authentifizieren, stellt der Authenticatorpasskey_user_id
in seiner Authentifizierungsantwort inuserHandle
zur Verfügung. Wir empfehlen,passkey_user_id
nicht als Primärschlüssel festzulegen. Primärschlüssel werden in Systemen häufig zu personenidentifizierbaren Informationen, da sie häufig verwendet werden.
- Tabelle mit Anmeldedaten für öffentlichen Schlüssel:
id
: Anmeldedaten-ID Verwenden Sie diesen Schlüssel als Primärschlüssel für Ihre Tabelle mit den Anmeldedaten für den öffentlichen Schlüssel.public_key
: Öffentlicher Schlüssel des Berechtigungsnachweises.passkey_user_id
: Verwenden Sie diesen als Fremdschlüssel, um eine Verknüpfung mit der Tabelle Users herzustellen.backed_up
: Ein Passkey wird gesichert, wenn er vom Passkey-Anbieter synchronisiert wird. Das Speichern des Sicherungsstatus ist nützlich, wenn Sie in Zukunft Passwörter für Nutzer mitbacked_up
Passkeys löschen möchten. Ob der Passkey gesichert ist, kannst du mithilfe der Flags inauthenticatorData
oder mithilfe einer serverseitigen FIDO-Bibliotheksfunktion prüfen, die normalerweise für einen einfachen Zugriff auf diese Informationen zur Verfügung steht. Es kann hilfreich sein, die Voraussetzungen für die Sicherung zu speichern, um potenzielle Nutzeranfragen zu bearbeiten.name
: Optional ein Anzeigename für die Anmeldedaten, über den Nutzer benutzerdefinierte Namen für Anmeldedaten eingeben könnentransports
: Ein Array von Transsports. Das Speichern von Transporten ist nützlich, um sich zu authentifizieren. Wenn Transporte verfügbar sind, kann sich der Browser entsprechend verhalten und eine UI anzeigen, die dem Transport entspricht, den der Passkey-Anbieter für die Kommunikation mit Clients verwendet. Das gilt insbesondere für Anwendungsfälle zur erneuten Authentifizierung, bei denenallowCredentials
nicht leer ist.
Andere Informationen können hilfreich sein, um die Nutzererfahrung zu verbessern, darunter Elemente wie der Passkey-Anbieter, der Zeitpunkt der Erstellung der Anmeldedaten und der Zeitpunkt der letzten Nutzung. Weitere Informationen finden Sie unter Design der Passkeys-Benutzeroberfläche.
Beispielcode: Anmeldedaten speichern
Wir verwenden in unseren Beispielen die SimpleWebAuthn-Bibliothek.
Hier übergeben wir die Bestätigung der Registrierungsantwort an die Funktion verifyRegistrationResponse
.
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/registerResponse', csrfCheck, sessionCheck, async (req, res) => {
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
const response = req.body;
// This sample code is for registering a passkey for an existing,
// signed-in user
// 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 {
// Verify the credential
const { verified, registrationInfo } = await verifyRegistrationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
requireUserVerification: false,
});
if (!verified) {
throw new Error('Verification failed.');
}
const { credentialPublicKey, credentialID } = registrationInfo;
// Existing, signed-in user
const { user } = res.locals;
// Save the credential
await Credentials.update({
id: base64CredentialID,
publicKey: base64PublicKey,
// Optional: set the platform as a default name for the credential
// (example: "Pixel 7")
name: req.useragent.platform,
transports: response.response.transports,
passkey_user_id: user.passkey_user_id,
backed_up: registrationInfo.credentialBackedUp
});
// Kill the challenge for this session
delete req.session.challenge;
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).send({ error: e.message });
}
});
Anhang: AuthenticatorAttestationResponse
AuthenticatorAttestationResponse
enthält zwei wichtige Objekte:
response.clientDataJSON
ist eine JSON-Version von Clientdaten. Dies sind Daten, die im Web für den Browser sichtbar sind. Sie enthält den RP-Ursprung, die Aufgabe undandroidPackageName
, falls es sich bei dem Client um eine Android-App handelt. Da es sich bei dem Client um eine RP handelt, erhalten Sie durch Lesen vonclientDataJSON
Zugriff auf Informationen, die der Browser zum Zeitpunkt dercreate
-Anfrage gesehen hat.response.attestationObject
enthält zwei Angaben:attestationStatement
, die nur dann relevant ist, wenn Sie die Attestierung verwenden.authenticatorData
sind die Daten, die der Passkey-Anbieter sieht. Als RP erhalten Sie durch Lesen vonauthenticatorData
Zugriff auf die Daten, die der Passkey-Anbieter gesehen und zum Zeitpunkt dercreate
-Anfrage zurückgegeben hat.
authenticatorData
enthält wichtige Informationen zu den Anmeldedaten für den öffentlichen Schlüssel, die mit dem neu erstellten Passkey verknüpft sind:
- Die Anmeldedaten des öffentlichen Schlüssels selbst und eine eindeutige Anmeldedaten-ID dafür.
- Die mit dem Berechtigungsnachweis verknüpfte RP-ID.
- Flags, die den Nutzerstatus beim Erstellen des Passkeys beschreiben: ob ein Nutzer tatsächlich anwesend war und ob er erfolgreich bestätigt wurde (siehe
userVerification
). - AAGUID: Gibt den Passkey-Anbieter an. Die Anzeige des Passkey-Anbieters kann für Ihre Nutzer hilfreich sein, insbesondere wenn sie einen Passkey bei mehreren Anbietern für Ihren Dienst registriert haben.
Obwohl authenticatorData
in attestationObject
verschachtelt ist, werden die darin enthaltenen Informationen für deine Passkey-Implementierung unabhängig davon benötigt, ob du die Attestierung verwendest oder nicht. authenticatorData
ist codiert und enthält Felder, die in einem Binärformat codiert sind. Das Parsen und Decodieren übernimmt in der Regel Ihre serverseitige Bibliothek. Wenn Sie keine serverseitige Bibliothek verwenden, sollten Sie getAuthenticatorData()
clientseitig nutzen, um sich serverseitig geparst und decodieren zu müssen.
Anhang: Bestätigung der Registrierungsantwort
Die Überprüfung der Registrierungsantwort umfasst die folgenden Überprüfungen:
- Stellen Sie sicher, dass die RP-ID Ihrer Website entspricht.
- Achten Sie darauf, dass der Ursprung der Anfrage ein erwarteter Ursprung für Ihre Website ist (Hauptwebsite-URL, Android-App).
- Wenn eine Nutzerbestätigung erforderlich ist, muss das Flag
authenticatorData.uv
für die Nutzerbestätigung auftrue
gesetzt sein. Prüfen Sie, ob das FlagauthenticatorData.up
für die Nutzerpräsenztrue
ist, da die Nutzerpräsenz für Passkeys immer erforderlich ist. - Überprüfen Sie, ob der Kunde in der Lage war, die Aufgabe zu lösen. Wenn Sie keine Attestierung verwenden, ist diese Prüfung unwichtig. Das Implementieren dieser Prüfung ist jedoch eine Best Practice: Sie stellt sicher, dass Ihr Code bereit ist, wenn Sie sich später für die Verwendung der Attestierung entscheiden.
- Die Anmeldedaten-ID darf noch nicht für einen Nutzer registriert sein.
- Prüfe, ob der vom Passkey-Anbieter zum Erstellen der Anmeldedaten verwendete Algorithmus von dir in jedem
alg
-Feld vonpublicKeyCredentialCreationOptions.pubKeyCredParams
angegeben wurde. Es ist normalerweise in deiner serverseitigen Bibliothek definiert und für dich nicht sichtbar. Dadurch wird sichergestellt, dass sich Nutzer nur mit Algorithmen registrieren können, die Sie zugelassen haben.
Weitere Informationen finden Sie im Quellcode für verifyRegistrationResponse
von SimpleWebAuthn. Die vollständige Liste der Überprüfungen finden Sie in der Spezifikation.