# 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: true` so any non-UTF-8 byte rejects with `WEBAUTHN.MALFORMED_RESPONSE`. - JSON parse with cause-chain preservation. - Zod `ClientDataJSONSchema` enforces `type`, `challenge`, `origin` fields; extra fields pass through for forward compatibility with future W3C revisions (`topOrigin`, `crossOrigin`, payment-extension fields). - Discriminated `expectedType` argument — `"webauthn.create"` for registration, `"webauthn.get"` for authentication — so callers cannot accidentally accept the wrong ceremony's response. - Constant-time `origin` and `challenge` compares via `constantTimeEqual` from `@suaveplan/crypto-utils`. - Base64url decode of `challenge` before compare so the byte-level comparison happens after canonicalisation. ## Usage: Basic Example ```ts import { parseClientDataJSON } from "@suaveplan/webauthn/parser"; const data = parseClientDataJSON(response.clientDataJSON); console.log(data.type, data.origin); ``` ## Usage: Advanced Example ```ts 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; throws `WEBAUTHN.MALFORMED_RESPONSE`. - `validateClientDataJSON(input: { clientDataJSON, expectedType, expectedOrigin, expectedChallenge }): ClientDataJSON` — full validation; throws `WEBAUTHN.MALFORMED_RESPONSE` (type), `WEBAUTHN.ORIGIN_MISMATCH`, or `WEBAUTHN.CHALLENGE_MISMATCH` on 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. - `origin` is compared as UTF-8 bytes via `constantTimeEqual` rather 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()` on `ClientDataJSONSchema` is 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. - `parseClientDataJSON` is the lower-level building block; consumers that already validated `type` / `origin` / `challenge` elsewhere can call it directly. `validateClientDataJSON` is the one-shot entry point used by `register` and `authenticate`.