Skip to content

feat: add WebAuthn/passkey SDK methods#40

Closed
lakhansamani wants to merge 2 commits into
feat/error-extensions-codefrom
feat/webauthn-passkey-sdk
Closed

feat: add WebAuthn/passkey SDK methods#40
lakhansamani wants to merge 2 commits into
feat/error-extensions-codefrom
feat/webauthn-passkey-sdk

Conversation

@lakhansamani

Copy link
Copy Markdown
Contributor

Summary

Wraps the 6 new webauthn_* GraphQL operations from authorizerdev/authorizer#671 and adds browser-side ceremony glue.

  • Raw methods: webauthnRegistrationOptions/Verify, webauthnLoginOptions/Verify, webauthnCredentials, webauthnDeleteCredential
  • High-level orchestration: registerPasskey(name?) and loginWithPasskey(email?) drive the full options → browser API → verify round trip in one call
  • isWebauthnSupported() exported for feature detection

Design note

src/webauthn.ts uses the browser's native PublicKeyCredential.parseCreationOptionsFromJSON/parseRequestOptionsFromJSON/credential.toJSON() rather than hand-rolled base64url↔ArrayBuffer conversion — confirmed by reading go-webauthn's Go struct tags directly that its JSON wire format is exactly the spec-defined RegistrationResponseJSON/AuthenticationResponseJSON shape those browser APIs are built around.

Base branch

This targets feat/error-extensions-code (#39), not mainwebauthnLoginVerify's error handling relies on errors[0].code, which only exists on that branch. Rebase onto main once #39 merges.

Test plan

  • __test__/webauthnMethods.test.ts — 7 new unit tests (mocked fetch) covering all 6 raw methods including the email-verification-gate error code
  • Full suite green: 68 tests, 6 suites (npx jest)
  • tsc --noEmit clean, build clean (ESM/CJS/IIFE)

Browser ceremony glue (registerPasskey/loginWithPasskey) isn't unit-testable in this Node/Jest environment (no real PublicKeyCredential) — will be exercised live in authorizer-react's example app against a real backend next.

Wraps the 6 new webauthn_* GraphQL operations (authorizerdev/authorizer#671)
and adds the browser-side ceremony glue.

Raw methods: webauthnRegistrationOptions/Verify, webauthnLoginOptions/Verify,
webauthnCredentials, webauthnDeleteCredential - GraphQL-only (no REST route),
so these call graphqlQuery directly rather than through dispatch.

High-level orchestration: registerPasskey(name?) and loginWithPasskey(email?)
drive the full options -> browser API -> verify round trip in one call.

src/webauthn.ts uses the browser's native PublicKeyCredential.
parseCreationOptionsFromJSON / parseRequestOptionsFromJSON / credential.
toJSON() rather than hand-rolled base64url<->ArrayBuffer conversion -
go-webauthn's JSON wire format is the spec-defined RegistrationResponseJSON/
AuthenticationResponseJSON shape those APIs are built around, confirmed by
reading the library's struct tags directly rather than assuming.

isWebauthnSupported() is exported for callers to feature-detect before
showing passkey UI.
registerPasskey/loginWithPasskey previously collapsed every
navigator.credentials.create/get rejection (including the very common
"user dismissed the passkey picker" case, a NotAllowedError) into a
generic Error with only a message - no way for a caller to distinguish
cancellation from a real failure the way GraphQL errors already allow
via .code.

wrapCeremonyError now attaches the DOMException's own standard .name
(NotAllowedError, InvalidStateError, etc.) as an additive,
non-enumerable .code - the same field GraphQL errors carry - so
callers get one consistent way to branch on error kind rather than
matching message text. Reuses the browser's own exception names
instead of inventing new ones.

Also fixed isWebauthnSupported to check for credential.toJSON()
support, not just the two parseOptionsFromJSON statics - all three
shipped together in the same WebAuthn Level 3 browser releases, but
checking only two left a gap where the capability check could pass
while the actual toJSON call later fails.
@lakhansamani lakhansamani force-pushed the feat/webauthn-passkey-sdk branch from 001697e to be799b6 Compare July 11, 2026 06:28
@lakhansamani

Copy link
Copy Markdown
Contributor Author

Rebased onto the updated #39 (non-enumerable code + shared toSDKError). Also addressed the remaining review findings for this PR: ceremony errors (registerPasskey/loginWithPasskey) now surface the browser's own DOMException.name (e.g. NotAllowedError for a cancelled/timed-out ceremony) as .code, using the same additive non-enumerable field GraphQL errors carry — reusing the browser's own exception names rather than inventing new ones. Also fixed isWebauthnSupported to check for credential.toJSON() support, not just the two parseOptionsFromJSON statics. Full suite green (69 tests).

@lakhansamani lakhansamani deleted the branch feat/error-extensions-code July 11, 2026 09:22
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.

1 participant