Initial Commit
This commit is contained in:
53
packages/identity/auth/webauthn/src/parser/client-data.md
Normal file
53
packages/identity/auth/webauthn/src/parser/client-data.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user