Signatur
Beschreibung
Die Schnittstelle PublicKeyCredential gehört zur Web Authentication API (WebAuthn) und erweitert Credential aus der Credential Management API. Ein solches Objekt bekommst du, wenn du mit navigator.credentials.create({ publicKey }) ein neues Schlüsselpaar anlegst (Registrierung) oder dich mit navigator.credentials.get({ publicKey }) anmeldest. Der private Schlüssel bleibt immer im Authenticator, also im Gerät (Touch ID, Windows Hello, Android) oder auf einem Hardware-Token wie dem YubiKey. Dein Server bekommt nur den öffentlichen Schlüssel und signierte Daten zu sehen.
Wichtige Instanz-Eigenschaften:
id/rawId: die Kennung der Anmeldeinformation, als Base64URL-String bzw. alsArrayBuffer.type: immer"public-key".response: bei der Registrierung einAuthenticatorAttestationResponse, bei der Anmeldung einAuthenticatorAssertionResponse.authenticatorAttachment:"platform"(im Gerät eingebaut) oder"cross-platform"(z. B. USB-Token, Smartphone).
Instanz-Methoden sind getClientExtensionResults() und toJSON() (liefert ein serialisierbares Objekt mit Base64URL-kodierten Feldern). Dazu kommen statische Methoden: isUserVerifyingPlatformAuthenticatorAvailable(), isConditionalMediationAvailable() (Passkey-Autofill), getClientCapabilities() sowie parseCreationOptionsFromJSON() und parseRequestOptionsFromJSON().
Einen Konstruktor gibt es nicht, new PublicKeyCredential() wirft einen TypeError. Die Objekte entstehen nur über navigator.credentials. Die Challenge muss dein Server erzeugen, und er muss auch die Antwort prüfen: Signatur, Origin, RP-ID und Signaturzähler.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $options.publicKey (create) Pflicht | PublicKeyCredentialCreationOptions | Wird an navigator.credentials.create() übergeben. Enthält challenge, rp (Relying Party), user, pubKeyCredParams (Algorithmen, z. B. -7 für ES256 und -257 für RS256), optional authenticatorSelection, timeout, attestation und excludeCredentials. |
|
| $options.publicKey (get) Pflicht | PublicKeyCredentialRequestOptions | Wird an navigator.credentials.get() übergeben. Enthält challenge, optional rpId, allowCredentials, userVerification und timeout. |
|
| $options.mediation | "silent" | "optional" | "conditional" | "required" | "optional" | Mit "conditional" erscheinen Passkeys im Autofill eines Eingabefelds mit autocomplete="username webauthn". |
| $options.signal | AbortSignal | Damit lässt sich eine laufende Zeremonie abbrechen, z. B. wenn der Autofill-Vorgang durch einen expliziten Login ersetzt werden soll. |
Beispiele
Unterstützung prüfen
const checkPasskeySupport = async () => {
if (!window.PublicKeyCredential) {
return { webauthn: false };
}
const [platform, autofill] = await Promise.all([
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
PublicKeyCredential.isConditionalMediationAvailable?.() ?? false
]);
return { webauthn: true, platform, autofill };
};
checkPasskeySupport().then(console.log);
Registrierung: Passkey anlegen und an den Server senden
const registerPasskey = async () => {
// Optionen (inkl. Challenge) liefert der Server als JSON
const optionsJSON = await fetch('/webauthn/register/options').then(r => r.json());
const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJSON);
const credential = await navigator.credentials.create({ publicKey });
console.log(credential.type, credential.authenticatorAttachment);
// toJSON() kodiert alle ArrayBuffer als Base64URL
await fetch('/webauthn/register/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credential.toJSON())
});
};
registerPasskey().catch(err => {
if (err.name === 'NotAllowedError') console.warn('Vom Nutzer abgebrochen');
else if (err.name === 'InvalidStateError') console.warn('Passkey existiert bereits');
else console.error(err);
});
Anmeldung ohne Hilfsmethoden (manuelle Kodierung)
const b64url = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const login = async (challenge /* Uint8Array vom Server */) => {
const cred = await navigator.credentials.get({
publicKey: {
challenge,
rpId: location.hostname,
userVerification: 'preferred',
timeout: 60000
}
});
const payload = {
id: cred.id,
rawId: b64url(cred.rawId),
type: cred.type,
response: {
clientDataJSON: b64url(cred.response.clientDataJSON),
authenticatorData: b64url(cred.response.authenticatorData),
signature: b64url(cred.response.signature),
userHandle: cred.response.userHandle ? b64url(cred.response.userHandle) : null
}
};
return fetch('/webauthn/login/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
};
Passkey-Autofill (Conditional UI)
// HTML: <input name="username" autocomplete="username webauthn">
const controller = new AbortController();
const startAutofill = async () => {
if (!(await PublicKeyCredential.isConditionalMediationAvailable?.())) return;
const optionsJSON = await fetch('/webauthn/login/options').then(r => r.json());
const cred = await navigator.credentials.get({
mediation: 'conditional',
signal: controller.signal,
publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON)
});
console.log('Angemeldet mit', cred.id);
};
startAutofill().catch(e => e.name !== 'AbortError' && console.error(e));
// Vor einem anderen Login-Weg: controller.abort();