Signatur
Beschreibung
Die Eigenschaft userHandle eines AuthenticatorAssertionResponse-Objekts enthält den Benutzer-Bezeichner (user handle), den die Relying Party bei der Registrierung über navigator.credentials.create() im Feld publicKey.user.id übergeben hat. Der Authenticator speichert diesen Wert zusammen mit dem Credential und gibt ihn bei einer Anmeldung über navigator.credentials.get() wieder zurück.
Besonders wichtig ist userHandle bei Passkeys bzw. auffindbaren Credentials (discoverable credentials, früher „resident keys“): Hier fragt die Webseite ohne Benutzernamen und ohne allowCredentials nach einem Credential. Der Server erfährt erst über userHandle, welches Konto sich gerade anmeldet, und kann dann den passenden öffentlichen Schlüssel für die Signaturprüfung laden.
Der Wert ist ein opaker ArrayBuffer mit höchstens 64 Byte. Er sollte keine personenbezogenen Daten (E-Mail-Adresse, Name) enthalten, sondern z. B. eine zufällig erzeugte ID sein. Wird ein nicht auffindbares Credential verwendet, darf der Authenticator den Wert weglassen – dann ist userHandle null.
Rückgabewert
ArrayBuffer mit dem bei der Registrierung gesetzten user.id oder null, wenn der Authenticator keinen Benutzer-Bezeichner liefert (typisch bei nicht auffindbaren Credentials).Beispiele
Passkey-Anmeldung ohne Benutzernamen und userHandle auslesen
const toBase64Url = (buffer) =>
btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const loginWithPasskey = async (challenge) => {
const credential = await navigator.credentials.get({
publicKey: {
challenge, // Uint8Array vom Server
rpId: 'example.com',
userVerification: 'preferred'
// kein allowCredentials => auffindbare Credentials (Passkeys)
}
});
const { userHandle } = credential.response;
if (userHandle === null) {
throw new Error('Authenticator hat keinen userHandle geliefert');
}
console.log('userHandle (Base64URL):', toBase64Url(userHandle));
return credential;
};
Assertion inklusive userHandle an den Server senden
const toBase64Url = (buffer) =>
btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const sendAssertion = async (credential) => {
const res = credential.response;
const payload = {
id: credential.id,
rawId: toBase64Url(credential.rawId),
type: credential.type,
response: {
clientDataJSON: toBase64Url(res.clientDataJSON),
authenticatorData: toBase64Url(res.authenticatorData),
signature: toBase64Url(res.signature),
// userHandle kann null sein
userHandle: res.userHandle ? toBase64Url(res.userHandle) : null
}
};
const response = await fetch('/webauthn/login/finish', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
return response.json();
};
Zusammenhang mit der Registrierung: user.id setzen
// Bei der Registrierung: zufällige, nicht personenbezogene ID erzeugen
const userId = crypto.getRandomValues(new Uint8Array(16));
const credential = await navigator.credentials.create({
publicKey: {
challenge: crypto.getRandomValues(new Uint8Array(32)),
rp: { name: 'Beispiel GmbH', id: 'example.com' },
user: { id: userId, name: 'anna@example.com', displayName: 'Anna' },
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
authenticatorSelection: { residentKey: 'required' }
}
});
// Später bei navigator.credentials.get() liefert
// credential.response.userHandle genau diese 16 Byte zurück.
console.log(userId.byteLength);
// Wichtig · Fallstricke
- Kann
nullsein: Bei Credentials, die nicht auffindbar sind (Anmeldung mitallowCredentials), darf der Authenticator den Wert weglassen. Code muss diesen Fall immer abfangen. - Keine personenbezogenen Daten: Die Spezifikation verlangt, dass
user.idbzw.userHandlekeine identifizierenden Informationen wie E-Mail oder Benutzername enthält – nutze zufällige Bytes (max. 64 Byte). - Nicht vertrauen, sondern prüfen: Der Server muss sicherstellen, dass der gelieferte
userHandlezum Credential (rawId) passt und die Signatur mit dem zugehörigen öffentlichen Schlüssel gültig ist. Der Wert allein ist kein Authentifizierungsnachweis. - Binärdaten:
ArrayBufferlässt sich nicht direkt perJSON.stringify()übertragen; vorher Base64URL-kodieren. Neuere Browser bieten dafürPublicKeyCredential.prototype.toJSON(). - Kompatibilität: WebAuthn funktioniert nur in sicheren Kontexten (HTTPS bzw.
localhost). In älteren Browsern ohne WebAuthn-Unterstützung existiert das Interface nicht.