3.3 KiB
3.3 KiB
Client data — parse + validate
Purpose
Decodes the clientDataJSON byte string returned by navigator.credentials.create / get into a typed object and validates its type, origin, and challenge fields against the relying-party's expectations. The clientDataJSON is the authenticator's anti-replay anchor — getting this validation right is the difference between a real WebAuthn deployment and a token-passing protocol that any phishing site can replay.
Features
- UTF-8 decode with
fatal: trueso any non-UTF-8 byte rejects withWEBAUTHN.MALFORMED_RESPONSE. - JSON parse with cause-chain preservation.
- Zod
ClientDataJSONSchemaenforcestype,challenge,originfields; extra fields pass through for forward compatibility with future W3C revisions (topOrigin,crossOrigin, payment-extension fields). - Discriminated
expectedTypeargument —"webauthn.create"for registration,"webauthn.get"for authentication — so callers cannot accidentally accept the wrong ceremony's response. - Constant-time
originandchallengecompares viaconstantTimeEqualfrom@suaveplan/crypto-utils. - Base64url decode of
challengebefore compare so the byte-level comparison happens after canonicalisation.
Usage: Basic Example
import { parseClientDataJSON } from "@suaveplan/webauthn/parser";
const data = parseClientDataJSON(response.clientDataJSON);
console.log(data.type, data.origin);
Usage: Advanced Example
import {
CLIENT_DATA_TYPE_CREATE,
validateClientDataJSON,
} from "@suaveplan/webauthn/parser";
validateClientDataJSON({
clientDataJSON: response.clientDataJSON,
expectedType: CLIENT_DATA_TYPE_CREATE,
expectedOrigin: "https://example.com",
expectedChallenge: storedChallengeBytes,
});
API Reference
CLIENT_DATA_TYPE_CREATE = "webauthn.create"— registration discriminator.CLIENT_DATA_TYPE_GET = "webauthn.get"— assertion discriminator.parseClientDataJSON(bytes: Uint8Array): ClientDataJSON— decode + schema validate; throwsWEBAUTHN.MALFORMED_RESPONSE.validateClientDataJSON(input: { clientDataJSON, expectedType, expectedOrigin, expectedChallenge }): ClientDataJSON— full validation; throwsWEBAUTHN.MALFORMED_RESPONSE(type),WEBAUTHN.ORIGIN_MISMATCH, orWEBAUTHN.CHALLENGE_MISMATCHon the appropriate failure.
Implementation Notes
- The challenge is compared byte-for-byte after base64url decode rather than string-for-string so authenticators that emit base64url with or without padding (some Yubikey firmware does the latter on long challenges) all canonicalise to the same byte string.
originis compared as UTF-8 bytes viaconstantTimeEqualrather than via===so a mismatch in the high-bit-set portion of an IDN origin (e.g. punycode-encoded host) takes time independent of the byte index of the difference.- The
passthrough()onClientDataJSONSchemais deliberate — the W3C spec reserves the right to add fields (tokenBinding,topOrigin, payment extension fields) and we do not want the parser to reject conforming new browsers. parseClientDataJSONis the lower-level building block; consumers that already validatedtype/origin/challengeelsewhere can call it directly.validateClientDataJSONis the one-shot entry point used byregisterandauthenticate.