Signatur
Beschreibung
Bei einer WebAuthn-Registrierung über navigator.credentials.create() liefert der Authenticator ein PublicKeyCredential, dessen response vom Typ AuthenticatorAttestationResponse ist. Die Methode getAuthenticatorData() gibt den Teil authData des attestationObject direkt als ArrayBuffer zurück. Früher musste man dafür das attestationObject selbst per CBOR-Bibliothek dekodieren.
Die Authenticator-Daten sind ein Binärformat mit festem Aufbau:
- Bytes 0–31:
rpIdHash, der SHA-256-Hash der Relying-Party-ID (z. B.example.com) - Byte 32: Flags: Bit 0 = User Present (UP), Bit 2 = User Verified (UV), Bit 3 = Backup Eligible (BE), Bit 4 = Backup State (BS), Bit 6 = Attested Credential Data (AT), Bit 7 = Extension Data (ED)
- Bytes 33–36:
signCountals 32-Bit-Big-Endian-Zähler - ab Byte 37 (wenn AT gesetzt ist): AAGUID (16 Bytes), Länge der Credential-ID (2 Bytes, Big Endian), die Credential-ID selbst und der öffentliche Schlüssel im COSE-Format (CBOR); danach folgen optional die Erweiterungsdaten
Auf der Client-Seite ist die Methode nützlich für Diagnose und UI-Entscheidungen, etwa um zu erkennen, ob ein Passkey synchronisiert wird (BE/BS-Flags). Außerdem kann man damit die Authenticator-Daten gezielt an den Server schicken. Die sicherheitsrelevante Prüfung (rpIdHash, Flags, Attestation-Signatur) muss aber immer auf dem Server laufen.
Rückgabewert
ArrayBuffer mit den rohen Authenticator-Daten (mindestens 37 Bytes, bei der Registrierung wegen der Attested Credential Data deutlich mehr). Er ist inhaltlich identisch mit dem Feld authData im attestationObject.Beispiele
Passkey registrieren und Flags auslesen
const registerPasskey = async () => {
const credential = await navigator.credentials.create({
publicKey: {
challenge: crypto.getRandomValues(new Uint8Array(32)), // in der Praxis vom Server
rp: { name: 'Beispiel GmbH', id: location.hostname },
user: {
id: new TextEncoder().encode('user-42'),
name: 'max@example.com',
displayName: 'Max Mustermann'
},
pubKeyCredParams: [{ type: 'public-key', alg: -7 }, { type: 'public-key', alg: -257 }],
authenticatorSelection: { residentKey: 'required', userVerification: 'preferred' }
}
});
const authData = new Uint8Array(credential.response.getAuthenticatorData());
const flags = authData[32];
console.log({
laenge: authData.byteLength,
userPresent: Boolean(flags & 0x01),
userVerified: Boolean(flags & 0x04),
backupEligible: Boolean(flags & 0x08),
backupState: Boolean(flags & 0x10),
attestedCredentialData: Boolean(flags & 0x40)
});
};
document.querySelector('#register').addEventListener('click', registerPasskey);
Binärstruktur zerlegen: signCount, AAGUID, Credential-ID
const parseAuthenticatorData = (buffer) => {
const view = new DataView(buffer);
const bytes = new Uint8Array(buffer);
const flags = view.getUint8(32);
const result = {
rpIdHash: bytes.slice(0, 32),
flags,
signCount: view.getUint32(33, false) // Big Endian
};
if (flags & 0x40) {
const toHex = (arr) => [...arr].map((b) => b.toString(16).padStart(2, '0')).join('');
result.aaguid = toHex(bytes.slice(37, 53));
const credIdLength = view.getUint16(53, false);
result.credentialId = bytes.slice(55, 55 + credIdLength);
result.cosePublicKey = bytes.slice(55 + credIdLength); // CBOR (+ ggf. Extensions)
}
return result;
};
// credential stammt aus navigator.credentials.create()
const info = parseAuthenticatorData(credential.response.getAuthenticatorData());
console.log(info.signCount, info.aaguid, info.credentialId.byteLength);
rpIdHash clientseitig gegenprüfen (Diagnose)
const checkRpIdHash = async (credential, rpId) => {
const authData = new Uint8Array(credential.response.getAuthenticatorData());
const expected = new Uint8Array(
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(rpId))
);
const actual = authData.slice(0, 32);
return expected.every((byte, i) => byte === actual[i]);
};
console.log(await checkRpIdHash(credential, location.hostname));
Authenticator-Daten als Base64URL an den Server senden
const toBase64Url = (buffer) =>
btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
const { response } = credential;
await fetch('/api/webauthn/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: credential.id,
clientDataJSON: toBase64Url(response.clientDataJSON),
attestationObject: toBase64Url(response.attestationObject),
authenticatorData: toBase64Url(response.getAuthenticatorData()),
transports: response.getTransports?.() ?? []
})
});