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

3.1 KiB

PRF extension support

(no submodules)

Purpose

Implements the W3C WebAuthn L3 §10.1.4 PRF (Pseudo-Random Function) extension passthrough plus result parsing. PRF is the cornerstone of passkey-as-master-key-unlock flows: the authenticator's secure element materialises a ~32-byte symmetric secret deterministic in the (credential id, caller-supplied salt) pair. Consumers (notably @suaveplan/vault) use the secret to wrap an Account Unlock Key so a vault unlocks without the user typing a master password.

Features

  • Typed input shapes for both ceremonies: PrfRegisterInput ({ eval: { first, second? } }) and PrfAuthenticateInput ({ eval | evalByCredential }).
  • readPrfResult(extensions) extracts the prf field from the raw ClientExtensionResults object, narrowing types so callers consume Uint8Arrays rather than unknown.
  • buildPrfExtension(input) produces the wire-shape forwarded to navigator.credentials.create/get so callers do not duplicate the structural construction.
  • Three-state result: enabled === false (not supported), enabled === true with no results (supported but caller did not request an eval), enabled === true with results.first: Uint8Array (the materialised secret).

Usage: Basic Example

import { readPrfResult } from "@suaveplan/webauthn/extensions/prf";

const prf = readPrfResult(authenticationResult.extensions);
if (prf?.enabled && prf.results?.first) {
    const wrapKey = prf.results.first;
}

Usage: Advanced Example

import {
    buildPrfExtension,
    readPrfResult,
} from "@suaveplan/webauthn/extensions/prf";

const ext = buildPrfExtension({
    eval: { first: new Uint8Array(32) }
});
const cred = await navigator.credentials.get({
    publicKey: { challenge, allowCredentials, extensions: ext },
});
const prf = readPrfResult(
    (cred as PublicKeyCredential).getClientExtensionResults()
);
if (!prf?.enabled) {
    throw new Error("authenticator does not support PRF");
}

API Reference

  • PrfRegisterInput — { eval?: { first: Uint8Array, second?: Uint8Array } }.
  • PrfAuthenticateInput — { eval?, evalByCredential? }.
  • PrfRegisterResult / PrfAuthenticateResult — { enabled: boolean, results?: { first: Uint8Array, second?: Uint8Array } }.
  • readPrfResult(extensions): PrfRegisterResult | undefined — narrow + extract.
  • buildPrfExtension(input): { prf?: ... } — wire-shape builder.

Implementation Notes

  • The package does NOT request or surface a prf second-input by default. Consumers that need two-input PRF (key rotation flows) opt in by setting eval.second.
  • The enabled field is set to true by some Chromium platform authenticators even when the caller did not request an eval; consumers SHOULD treat enabled === true without results as "PRF is available, request it next time" rather than "secret is materialised".
  • readPrfResult is intentionally tolerant of missing fields — it returns undefined when no PRF extension is present at all, distinguishing that from { enabled: false } (PRF requested but not supported by the authenticator).