Start · Sprachen · JavaScript · Referenz · AuthenticatorAssertionResponse.authenticatorData

AuthenticatorAssertionResponse.authenticatorData

Eigenschaft

Nur lesbares <code>ArrayBuffer</code> mit den Authentifikator-Daten einer WebAuthn-Anmeldung: rpIdHash, Flags, Signaturzähler und optionale Erweiterungen.

seit JavaScript Baseline: alle modernen Browser (WebAuthn Level 1, Kategorie: webapi

Signatur

AuthenticatorAssertionResponse.authenticatorData

Beschreibung

Die Eigenschaft authenticatorData des Interfaces AuthenticatorAssertionResponse enthält die Rohdaten, die der Authentifikator (Sicherheitsschlüssel, Passkey, Plattform-Authentifikator wie Touch ID oder Windows Hello) bei einer Anmeldung über navigator.credentials.get() erzeugt. Du erreichst sie über credential.response.authenticatorData. Der Wert ist ein ArrayBuffer, den du zur Prüfung an den Server schickst.

Der Puffer ist binär aufgebaut:

  • Bytes 0–31 – rpIdHash: SHA-256-Hash der Relying-Party-ID (z. B. example.com). Der Server vergleicht ihn mit dem Hash seiner eigenen RP-ID.
  • 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 – signCount: 32-Bit-Signaturzähler (Big Endian), mit dem geklonte Authentifikatoren erkannt werden können.
  • Ab Byte 37 (optional): CBOR-kodierte Erweiterungsdaten, wenn das ED-Flag gesetzt ist.

Die Signatur in response.signature wird über die Verkettung von authenticatorData und dem SHA-256-Hash von clientDataJSON gebildet. Der Server muss deshalb authenticatorData byte-genau so erhalten, wie der Browser es geliefert hat. Nur dann kann er die Signatur mit dem gespeicherten öffentlichen Schlüssel prüfen.

Rückgabewert

Typ
ArrayBuffer
Beschreibung
Nur lesbarer Binärpuffer (mindestens 37 Bytes) mit rpIdHash, Flags, Signaturzähler und optionalen Erweiterungsdaten.

Beispiele

Anmeldung durchführen und authenticatorData an den Server senden

// ArrayBuffer -> base64url für den JSON-Transport
const toBase64Url = (buf) =>
  btoa(String.fromCharCode(...new Uint8Array(buf)))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

const login = async (challengeFromServer) => {
  const credential = await navigator.credentials.get({
    publicKey: {
      challenge: challengeFromServer, // Uint8Array vom Server
      rpId: location.hostname,
      userVerification: 'preferred'
    }
  });

  const { authenticatorData, clientDataJSON, signature, userHandle } = credential.response;

  await fetch('/webauthn/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      id: credential.id,
      authenticatorData: toBase64Url(authenticatorData),
      clientDataJSON: toBase64Url(clientDataJSON),
      signature: toBase64Url(signature),
      userHandle: userHandle ? toBase64Url(userHandle) : null
    })
  });
};

Flags und Signaturzähler auslesen

const parseAuthenticatorData = (buffer) => {
  const view = new DataView(buffer);
  const flags = view.getUint8(32);

  return {
    rpIdHash: new Uint8Array(buffer, 0, 32),
    userPresent: Boolean(flags & 0x01),
    userVerified: Boolean(flags & 0x04),
    backupEligible: Boolean(flags & 0x08),
    backedUp: Boolean(flags & 0x10),
    hasExtensions: Boolean(flags & 0x80),
    signCount: view.getUint32(33, false) // Big Endian
  };
};

// Nach navigator.credentials.get():
// const info = parseAuthenticatorData(credential.response.authenticatorData);
// console.log(info);
{ rpIdHash: Uint8Array(32) [...], userPresent: true, userVerified: true, backupEligible: true, backedUp: true, hasExtensions: false, signCount: 0 }

rpIdHash gegen die erwartete RP-ID prüfen

const checkRpIdHash = async (authenticatorData, expectedRpId) => {
  const expected = new Uint8Array(
    await crypto.subtle.digest('SHA-256', new TextEncoder().encode(expectedRpId))
  );
  const actual = new Uint8Array(authenticatorData, 0, 32);
  return expected.every((byte, i) => byte === actual[i]);
};

// const ok = await checkRpIdHash(credential.response.authenticatorData, 'example.com');
// console.log(ok);
true

// Wichtig · Fallstricke

  • Immer auf dem Server prüfen: Das Auslesen im Browser eignet sich nur zur Anzeige oder zur Fehlersuche. rpIdHash, UP/UV-Flags, Signaturzähler und Signatur musst du auf dem Server prüfen, denn Daten vom Client sind nicht vertrauenswürdig. Nutze dafür am besten eine erprobte Bibliothek (z. B. SimpleWebAuthn, py_webauthn oder webauthn4j).
  • Signaturzähler: Viele Passkey-Anbieter mit Synchronisierung (Apple, Google) liefern immer 0. Nur wenn der Zähler ungleich 0 ist, sollte der Server prüfen, ob er gegenüber dem gespeicherten Wert gestiegen ist. Sonst lehnst du gültige Anmeldungen ab.
  • Nicht verändern: Du darfst den Puffer vor dem Senden weder dekodieren noch neu zusammensetzen. Schon ein abweichendes Byte macht die Signaturprüfung ungültig. Für den JSON-Transport eignet sich base64url.
  • Abgrenzung: Bei der Registrierung (AuthenticatorAttestationResponse) stecken die Authentifikator-Daten im CBOR-Attestierungsobjekt. Sie lassen sich dort auch direkt über getAuthenticatorData() abrufen.
  • Kompatibilität: Die Eigenschaft wird in allen aktuellen Browsern (Chrome, Edge, Firefox, Safari) unterstützt. WebAuthn läuft nur in einem sicheren Kontext (HTTPS oder localhost). Seit toJSON() und PublicKeyCredential.parseRequestOptionsFromJSON() gibt es einen eingebauten Weg zur Serialisierung, den ältere Browser aber noch nicht kennen.