Files
skills/docs/identity/auth/webauthn/src/extensions/large-blob/large-blob.md
2026-05-29 08:20:56 +01:00

3.8 KiB

largeBlob extension support

(no submodules)

Purpose

Implements the W3C WebAuthn L3 §10.1.3 largeBlob extension passthrough plus result parsing. The largeBlob extension lets the authenticator store a small per-credential byte string (typically credentials or session-resumption tokens) inside the authenticator's secure element. Registration asks the authenticator to allocate the slot; subsequent authentications read or write the blob.

Features

  • LargeBlobRegisterInput ({ support: "required" | "preferred" }) and discriminated LargeBlobAuthenticateInput ({ read: true } xor { write: Uint8Array }) — W3C-mandated mutual exclusion.
  • readLargeBlobRegisterResult extracts { supported } from the registration ceremony's ClientExtensionResults.
  • readLargeBlobReadResult extracts { blob? } from the assertion ceremony's results — blob is omitted (not zeroed) when the authenticator did not return one.
  • readLargeBlobWriteResult extracts { written } — boolean discriminator between success and silent drop.
  • buildLargeBlobRegisterExtension / buildLargeBlobAuthenticateExtension produce the wire-shape passed to navigator.credentials.{create,get}.

Usage: Basic Example

import {
    buildLargeBlobAuthenticateExtension,
    readLargeBlobReadResult,
} from "@suaveplan/webauthn/extensions/large-blob";

const ext = buildLargeBlobAuthenticateExtension({ read: true });
const cred = await navigator.credentials.get({
    publicKey: { challenge, allowCredentials, extensions: ext }
});
const result = readLargeBlobReadResult(
    (cred as PublicKeyCredential).getClientExtensionResults()
);
console.log(result?.blob?.byteLength ?? 0);

Usage: Advanced Example

import {
    buildLargeBlobAuthenticateExtension,
    buildLargeBlobRegisterExtension,
    readLargeBlobRegisterResult,
    readLargeBlobWriteResult,
} from "@suaveplan/webauthn/extensions/large-blob";

const regExt = buildLargeBlobRegisterExtension({ support: "required" });
const reg = await navigator.credentials.create({
    publicKey: { ...opts, extensions: regExt }
});
const regResult = readLargeBlobRegisterResult(
    (reg as PublicKeyCredential).getClientExtensionResults()
);
if (!regResult?.supported) {
    throw new Error("authenticator does not support largeBlob");
}

const writeExt = buildLargeBlobAuthenticateExtension({ write: payload });
const authResult = await navigator.credentials.get({
    publicKey: { ...authOpts, extensions: writeExt }
});
const w = readLargeBlobWriteResult(
    (authResult as PublicKeyCredential).getClientExtensionResults()
);
if (!w?.written) {
    throw new Error("write dropped");
}

API Reference

  • LargeBlobRegisterInput, LargeBlobAuthenticateInput — input shapes.
  • LargeBlobRegisterResult, LargeBlobReadResult, LargeBlobWriteResult — result shapes.
  • readLargeBlobRegisterResult, readLargeBlobReadResult, readLargeBlobWriteResult — narrowing readers.
  • buildLargeBlobRegisterExtension, buildLargeBlobAuthenticateExtension — wire-shape builders.

Implementation Notes

  • The support: "required" mode causes the registration to fail (with NotSupportedError) when the authenticator cannot allocate a largeBlob slot; support: "preferred" allocates if possible and falls back silently otherwise. The result's supported flag distinguishes the two outcomes.
  • Read and write are mutually exclusive per W3C §10.1.3. The discriminated input union enforces this at the type level so callers cannot accidentally pass both.
  • readLargeBlobReadResult returns {} (empty object, not undefined) when the extension was acknowledged but no blob was returned. This lets callers distinguish "authenticator does not implement the extension" (undefined return) from "extension implemented, no blob stored" ({ blob: undefined }).