Skip to content

Publish a union for the WebAuthn error codes - #52

Merged
Bccorb merged 1 commit into
mainfrom
feat/webauthn-error-codes
Aug 30, 2026
Merged

Publish a union for the WebAuthn error codes#52
Bccorb merged 1 commit into
mainfrom
feat/webauthn-error-codes

Conversation

@Bccorb

@Bccorb Bccorb commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Closes #51.

Publishes WEBAUTHN_ERROR_CODES, WebAuthnErrorCodeSchema and
WebAuthnErrorCode for the machine-readable codes the auth API returns as the
whole of an error body's error field:

Code Status Endpoint
attachment_not_allowed 400 GET /webauthn/register/start
synced_passkey_not_allowed 403 POST /webauthn/register/finish
authenticator_not_allowed 403 POST /webauthn/register/finish
prf_required 403 POST /webauthn/register/finish
prf_output_not_allowed 400 POST /webauthn/login/finish

Enumerated by reading src/controllers/webauthn.ts and
src/services/authenticatorPolicyService.ts in seamless-auth-api, not from
memory. Every other failure on those endpoints answers with prose
(Not allowed, Missing challenge, Registration failed verification), so they
are deliberately absent, and the code comment says so rather than leaving the
next reader to wonder.

Why

This mirrors OAUTH_ERROR_CODES and exists for the same reason. A consumer that
declares its own copy has no way to find out when the API adds a code: it
degrades to generic messaging and nothing fails anywhere.

The React SDK already relies on the OAuth version working this way:

const OAUTH_ERROR_CODES: Record<OAuthErrorCode, true> = { ... };

That map stops compiling when the upstream union changes. Its WebAuthn
counterpart (fells-code/seamless-auth-react#133) had to declare a local union, so
its map only checks itself. This is the same class of silent drift that let the
API's authenticator_policy default fall behind this package's schema twice in
recent releases.

One decision worth reviewing

One union covers all five codes rather than a narrower one for the registration
policy refusals only.

For: a consumer asks a single question, "is this a code I know", and the grouping
does not need revisiting when a code is added on a different endpoint.

Against: it puts a client bug (prf_output_not_allowed, which means the client
sent PRF output it should never have transmitted) next to deployment policy
refusals, which are a different kind of thing. A consumer that cares can still
narrow further on top of this union.

I went with the single union. Happy to split it if you would rather the type
carried that distinction.

Tests

Three cases in src/schemas/webauthn/schema.test.ts: every published code
parses, an unpublished one throws, and a Record<WebAuthnErrorCode, true> is
compared against the list so the union and the array cannot drift from each
other. That last one is the property that makes the consumer-side check
meaningful, so it is worth asserting here rather than assuming.

Checks

npm run typecheck, npm run lint, npm run format:check and npm test all
pass (211 tests). npm run build succeeds and the symbols are present in
dist/schemas/webauthn/schema.d.ts.

Ripple

Additive, so a minor bump, and nothing changes for a consumer that does not
import it. Once released:

  • seamless-auth-api can drop AuthenticatorRefusal from
    src/services/authenticatorPolicyService.ts and the bare prf_required
    literal in src/controllers/webauthn.ts
  • seamless-auth-react can swap its local union for this one and restore the
    compile-time exhaustiveness check, replacing the note in its AGENTS.md that
    currently records the drift risk

Closes #51

The auth API returns machine-readable codes as the whole of an error body's
error field on the WebAuthn endpoints, and this package did not publish them, so
every consumer redeclared the list and no consumer found out when the API added
one.

OAUTH_ERROR_CODES already solves this for the OAuth surface, and the React SDK
leans on it: a local Record<OAuthErrorCode, true> stops compiling when the
upstream union changes. The WebAuthn codes had no such union, so the equivalent
SDK helper declared its own and its membership map only checked itself. That is
the same silent drift that let the API's authenticator_policy default fall behind
this package's schema twice.

One union covers all five rather than a narrower one for the registration policy
refusals. A consumer asks a single question, is this a code I know, and the
grouping does not have to be revisited when a code is added on a different
endpoint. It does put a client bug, prf_output_not_allowed, next to deployment
policy refusals, which are different in kind, and a consumer that cares about
that distinction can still narrow further.

The prose failures are deliberately absent rather than forgotten, and the code
says so.
@Bccorb
Bccorb merged commit 880e1d5 into main Aug 30, 2026
1 check passed
@Bccorb
Bccorb deleted the feat/webauthn-error-codes branch August 30, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Publish a union for the WebAuthn error codes, as OAUTH_ERROR_CODES does

1 participant