Start · Sprachen · JavaScript · Referenz · AuthenticatorResponse.clientDataJSON

AuthenticatorResponse.clientDataJSON

Eigenschaft

Nur lesbarer <code>ArrayBuffer</code> mit UTF-8-kodiertem JSON, das die Client-Daten (Typ, Challenge, Origin) einer WebAuthn-Registrierung oder -Anmeldung enthält.

seit JavaScript Baseline: alle modernen Browser (seit 2019/2020), Kategorie: webapi

Signatur

AuthenticatorResponse.clientDataJSON

Beschreibung

Die schreibgeschützte Eigenschaft clientDataJSON der Schnittstelle AuthenticatorResponse enthält die sogenannten Client-Daten. Der Browser stellt sie bei einem Aufruf von navigator.credentials.create() (Registrierung) oder navigator.credentials.get() (Anmeldung) zusammen. Es handelt sich um einen JSON-String, der als UTF-8-Bytefolge in einem ArrayBuffer liegt. Verfügbar ist die Eigenschaft sowohl auf AuthenticatorAttestationResponse als auch auf AuthenticatorAssertionResponse.

Das dekodierte Objekt enthält unter anderem:

  • type: "webauthn.create" bei der Registrierung, "webauthn.get" bei der Anmeldung.
  • challenge: die vom Server erzeugte Challenge, base64url-kodiert.
  • origin: der vollständige Origin der aufrufenden Seite, z. B. "https://example.com".
  • crossOrigin (optional): true, wenn der Aufruf aus einem Cross-Origin-iframe kam.
  • topOrigin (optional): der Origin des obersten Dokuments bei Cross-Origin-Aufrufen.

Die Signatur des Authenticators wird über authenticatorData zusammen mit dem SHA-256-Hash der exakten Bytes von clientDataJSON gebildet. Deshalb schickt man den Puffer unverändert an den Server, meist base64url-kodiert. Dort werden type, challenge und origin geprüft, danach wird die Signatur verifiziert.

Rückgabewert

Typ
ArrayBuffer
Beschreibung
Ein ArrayBuffer mit dem UTF-8-kodierten JSON der Client-Daten. Mit new TextDecoder().decode() und JSON.parse() wird daraus ein Objekt.

Beispiele

Client-Daten nach der Registrierung dekodieren

// challenge und userId kommen in der Praxis vom Server
const challenge = crypto.getRandomValues(new Uint8Array(32));

const credential = await navigator.credentials.create({
  publicKey: {
    challenge,
    rp: { name: 'Beispiel GmbH' },
    user: {
      id: crypto.getRandomValues(new Uint8Array(16)),
      name: 'max@example.com',
      displayName: 'Max Mustermann'
    },
    pubKeyCredParams: [{ type: 'public-key', alg: -7 }]
  }
});

const json = new TextDecoder().decode(credential.response.clientDataJSON);
const clientData = JSON.parse(json);
console.log(clientData);
{ type: 'webauthn.create', challenge: 'q3v…Xw', origin: 'https://example.com', crossOrigin: false }

Assertion base64url-kodiert an den Server senden

const toBase64Url = (buffer) =>
  btoa(String.fromCharCode(...new Uint8Array(buffer)))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

const login = async (challenge) => {
  const assertion = await navigator.credentials.get({
    publicKey: { challenge, userVerification: 'preferred' }
  });

  const { response } = assertion;
  await fetch('/webauthn/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      id: assertion.id,
      clientDataJSON: toBase64Url(response.clientDataJSON), // Bytes unverändert!
      authenticatorData: toBase64Url(response.authenticatorData),
      signature: toBase64Url(response.signature)
    })
  });
};

Serverseitige Prüfung der Client-Daten (Node.js)

import { createHash } from 'node:crypto';

const verifyClientData = (clientDataB64, expectedChallenge, expectedOrigin) => {
  const raw = Buffer.from(clientDataB64, 'base64url');
  const data = JSON.parse(raw.toString('utf8'));

  if (data.type !== 'webauthn.get') throw new Error('Falscher Typ');
  if (data.challenge !== expectedChallenge) throw new Error('Challenge ungültig');
  if (data.origin !== expectedOrigin) throw new Error('Origin ungültig');

  // Dieser Hash fließt in die Signaturprüfung ein
  return createHash('sha256').update(raw).digest();
};

console.log(verifyClientData(
  Buffer.from(JSON.stringify({ type: 'webauthn.get', challenge: 'abc', origin: 'https://example.com' })).toString('base64url'),
  'abc',
  'https://example.com'
).length);
32

// Wichtig · Fallstricke

  • Nicht neu serialisieren: Der Hash für die Signatur wird über die Original-Bytes gebildet. Wenn du das JSON parst und mit JSON.stringify() neu erzeugst, ändern sich Reihenfolge oder Leerzeichen, und die Prüfung schlägt fehl.
  • Die Challenge ist base64url-kodiert: Sie steht also nicht als Standard-Base64 im JSON, sondern mit - und _ und ohne =-Auffüllung. Vergleiche sie entsprechend.
  • Sicherheit: Prüfe origin, type und die Challenge immer serverseitig, und nutze die Challenge nur einmal (Schutz vor Wiederholungsangriffen). Eine Prüfung im Client allein bietet keinen Schutz vor Phishing oder manipulierten Clients.
  • Zusätzliche Felder: Das JSON kann weitere, auch künftige Felder enthalten, z. B. topOrigin. Deshalb nicht strikt auf eine feste Feldliste prüfen.
  • Kompatibilität: Die Eigenschaft funktioniert nur in sicheren Kontexten (HTTPS oder localhost). In Chrome, Edge, Firefox und Safari wird sie seit Jahren unterstützt. Für die Serialisierung gibt es neuere Hilfsmittel wie PublicKeyCredential.prototype.toJSON(), die noch nicht überall verfügbar sind.