Signatur
Beschreibung
PublicKeyCredentialRequestOptions ist ein Dictionary (ein einfaches Objekt, kein Konstruktor) aus der Web Authentication API (WebAuthn). Es wird als Wert der Option publicKey an navigator.credentials.get() übergeben und beschreibt, wie sich ein Benutzer mit einem bereits registrierten Passkey, Sicherheitsschlüssel (z. B. YubiKey) oder Plattform-Authenticator (Touch ID, Windows Hello, Android) anmelden soll.
Das wichtigste Feld ist challenge: ein vom Server zufällig erzeugter, nur einmal gültiger Wert. Der Authenticator signiert ihn mit dem privaten Schlüssel. Der Server prüft die Signatur anschließend mit dem bei der Registrierung gespeicherten öffentlichen Schlüssel. So lassen sich Wiederholungsangriffe (Replay) verhindern. Mit allowCredentials legst du fest, welche Credentials erlaubt sind. Lässt du das Feld leer, bekommst du eine „nutzernamenlose“ Anmeldung mit auffindbaren Credentials (Discoverable Credentials, also Passkeys).
Normalerweise erzeugt der Server die Optionen (Challenge, rpId, erlaubte Credential-IDs) und schickt sie als JSON an den Client. Weil binäre Felder als ArrayBuffer bzw. TypedArray erwartet werden, musst du Base64URL-Strings vorher umwandeln. Moderne Browser bieten dafür PublicKeyCredential.parseRequestOptionsFromJSON().
Das Ergebnis von get() ist ein PublicKeyCredential, dessen response vom Typ AuthenticatorAssertionResponse ist. Diese Antwort (authenticatorData, clientDataJSON, signature, userHandle) schickst du zur Prüfung an den Server.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $challenge Pflicht | ArrayBuffer | TypedArray | DataView | Vom Server erzeugter, kryptografisch zufälliger Wert (mind. 16 Byte empfohlen). Der Authenticator signiert ihn. Er darf nur einmal verwendet werden. | |
| $rpId | string | Effektive Domain des aktuellen Ursprungs | ID der Relying Party, also eine Domain wie example.com. Sie muss zur aktuellen Domain passen (gleich oder übergeordnet) und mit der bei der Registrierung verwendeten übereinstimmen. |
| $allowCredentials | Array<{ type: 'public-key', id: BufferSource, transports?: string[] }> | [] | Liste der erlaubten Credentials. Ist sie leer, darf der Authenticator jedes auffindbare Credential (Passkey) für die rpId anbieten. transports (z. B. 'usb', 'nfc', 'ble', 'internal', 'hybrid') sind Hinweise für den Browser. |
| $userVerification | 'required' | 'preferred' | 'discouraged' | 'preferred' | Legt fest, ob der Benutzer lokal verifiziert werden soll (PIN, Biometrie). Bei 'required' schlägt die Anmeldung fehl, wenn keine Verifikation möglich ist. |
| $timeout | number | Vom Browser abhängig | Empfohlene maximale Wartezeit in Millisekunden. Der Browser darf diesen Wert anpassen. |
| $hints | Array<'security-key' | 'client-device' | 'hybrid'> | [] | Hinweise an den Browser, welche Art von Authenticator in der Oberfläche bevorzugt werden soll (WebAuthn Level 3, noch nicht überall unterstützt). |
| $extensions | object | {} | WebAuthn-Erweiterungen, z. B. appid (Migration von U2F), largeBlob oder prf. Die Ergebnisse liefert credential.getClientExtensionResults(). |
Rückgabewert
navigator.credentials.get({ publicKey }) gibt ein Promise<PublicKeyCredential | null> zurück, dessen response eine AuthenticatorAssertionResponse ist.Beispiele
Einfache Anmeldung mit bekannter Credential-ID
// Hilfsfunktion: Base64URL → Uint8Array
const b64urlToBytes = (s) => {
const b64 = s.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(s.length / 4) * 4, '=');
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
};
const login = async () => {
// Server liefert Challenge und erlaubte Credential-IDs
const res = await fetch('/webauthn/login/options', { method: 'POST' });
const opts = await res.json();
/** @type {PublicKeyCredentialRequestOptions} */
const publicKey = {
challenge: b64urlToBytes(opts.challenge),
rpId: 'example.com',
allowCredentials: opts.credentialIds.map((id) => ({
type: 'public-key',
id: b64urlToBytes(id),
transports: ['internal', 'hybrid', 'usb']
})),
userVerification: 'preferred',
timeout: 60000
};
const credential = await navigator.credentials.get({ publicKey });
console.log(credential.type, credential.id);
return credential;
};
login().catch((err) => console.error(err.name, err.message));
Passkey-Anmeldung ohne Benutzernamen und Senden an den Server
const bytesToB64url = (buf) =>
btoa(String.fromCharCode(...new Uint8Array(buf)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const passkeyLogin = async (challengeBytes) => {
const credential = await navigator.credentials.get({
publicKey: {
challenge: challengeBytes,
allowCredentials: [], // leer = Passkey-Auswahl anzeigen
userVerification: 'required' // Biometrie/PIN erzwingen
}
});
const { authenticatorData, clientDataJSON, signature, userHandle } = credential.response;
const verify = await fetch('/webauthn/login/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: credential.id,
authenticatorData: bytesToB64url(authenticatorData),
clientDataJSON: bytesToB64url(clientDataJSON),
signature: bytesToB64url(signature),
userHandle: userHandle ? bytesToB64url(userHandle) : null
})
});
console.log('Anmeldung ok:', verify.ok);
};
passkeyLogin(crypto.getRandomValues(new Uint8Array(32))); // nur Demo, Challenge gehört vom Server!
JSON-Optionen parsen und Autofill (Conditional UI) nutzen
// HTML: <input type="text" autocomplete="username webauthn">
const startConditionalLogin = async () => {
if (!window.PublicKeyCredential?.isConditionalMediationAvailable) return;
if (!(await PublicKeyCredential.isConditionalMediationAvailable())) return;
const json = await (await fetch('/webauthn/login/options')).json();
// Wandelt Base64URL-Felder automatisch in ArrayBuffer um (sofern unterstützt)
const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON
? PublicKeyCredential.parseRequestOptionsFromJSON(json)
: json; // sonst manuell konvertieren
const abort = new AbortController();
const credential = await navigator.credentials.get({
publicKey,
mediation: 'conditional', // Passkeys im Autofill-Menü anbieten
signal: abort.signal
});
console.log('Gewählt:', credential.id);
console.log(JSON.stringify(credential.toJSON?.() ?? {}).slice(0, 40));
};
startConditionalLogin();