Skip to content

feat(passkey): let a caller ask for a security key at enrollment - #137

Merged
Bccorb merged 1 commit into
mainfrom
feat/passkey-attachment
Aug 30, 2026
Merged

feat(passkey): let a caller ask for a security key at enrollment#137
Bccorb merged 1 commit into
mainfrom
feat/passkey-attachment

Conversation

@Bccorb

@Bccorb Bccorb commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Closes #136. Client half of fells-code/seamless-auth-api#183.

Summary

GET /webAuthn/register/start accepts an attachment that narrows the browser
picker to one kind of authenticator, and nothing in this SDK could ask for it.
buildRegisterStartPath set only the PRF flags, so reaching the parameter meant
hand building the URL and reimplementing the PRF, metadata and error handling
registerPasskey already does.

registerPasskey() now takes an optional attachment, and the bundled enrolment
view offers a "Use a security key instead" control that takes it. That is the
path an organisation handing someone an issued key needs, rather than leaving
them to recognise it in a browser dialog.

Omitting the option sends no query parameter, so the deployment's
authenticator_policy.attachment stays in charge and the default enrolment path
is unchanged. There is a test pinning that specifically.

PasskeyAttachment

Derived as Exclude<AuthenticatorAttachmentPolicy, 'any'> rather than restating
the two strings. any is a standing deployment default, not something a single
request can ask for, so the request type is that policy minus that member. The
API's own request-side union lives in the API repository and is not published in
@seamless-auth/types, so deriving it is what keeps this from drifting the way
PasskeyPolicyErrorCode did before #132.

attachment_not_allowed is now in scope

This reverses part of #135, for a reason that only became true here.

That PR excluded attachment_not_allowed on two grounds: it is a 400 from
register/start rather than a 403 from register/finish, and nothing in the SDK
could provoke it, since the API only sends it when the caller names an
attachment. Sending the parameter is exactly what makes it reachable, and it is
a refusal by authenticator_policy like the others, so it belongs with them.
getPasskeyPolicyErrorCode is now documented as covering both stages.

prf_output_not_allowed stays out on the surviving half of that argument: it
reports a client that failed to strip PRF output, not a deployment refusing an
authenticator, and folding it in would point an integrator at their config for
what is a bug in the caller. Still pinned by a test.

Worth noting this is free to change: getPasskeyPolicyErrorCode has not shipped
yet. The published version is 0.9.0 and its changeset is still pending, so no
consumer has the narrower union.

Enrolment view

The view previously swallowed every failure into "Error registering passkey."
A policy refusal names something the user can act on, so it now renders a
specific message and keeps the generic one for everything else. This is what
#132 asked for in its acceptance criteria and could not fully deliver from the
client layer alone.

Type of Change

  • Feature
  • Fix
  • Refactor
  • Documentation
  • Test

Release Impact

  • Changeset added for adopter-facing package change
  • No package release expected

Two changesets. tidy-moons-repeat is new and covers the attachment option.
olive-pears-shout is the pending, unreleased one for getPasskeyPolicyErrorCode,
edited rather than supplemented: the helper ships in the same release, so a
changelog describing a three-code union followed by an entry widening it would
describe a history adopters never saw.

Checklist

  • Tests pass
  • No breaking changes
  • Changeset summary is clear for SDK adopters
  • Docs updated (if needed)
  • Security implications considered

npm run typecheck, npm test (315 passing, up from 305), npm run lint,
npm run format:check and npm run build all pass.

The one behavioural change to an existing path is that the enrolment view now
calls registerPasskey({ metadata, attachment }) instead of
registerPasskey(metadata). Both forms are accepted by normalizeRegisterInput,
and the existing test was updated to the new shape.

Closes #136.

GET /webAuthn/register/start accepts an attachment that narrows the browser
picker to one kind of authenticator, and nothing in this SDK could ask for it:
buildRegisterStartPath set only the PRF flags, so reaching the parameter meant
hand building the URL and reimplementing the PRF, metadata and error handling
registerPasskey already does.

registerPasskey now takes an optional attachment, and the bundled enrolment view
offers a "Use a security key instead" control that takes it. That is the path an
organisation handing someone an issued key needs, rather than leaving them to
recognise it in a browser dialog.

Omitting the option sends no query parameter, so the deployment's
authenticator_policy.attachment stays in charge and the default path is
unchanged. PasskeyAttachment is derived from AuthenticatorAttachmentPolicy with
Exclude rather than restating the two strings: `any` is a standing deployment
default, not something a single request can ask for, so the request type is that
policy minus that member. The API publishes no request-side union of its own.

PasskeyPolicyErrorCode gains attachment_not_allowed. It was left out in #135
because it was a 400 from register/start rather than a 403 from finish, and
because nothing in the SDK could provoke it. Sending the parameter is what makes
it reachable, and it is a refusal by authenticator_policy like the others, so it
belongs with them. prf_output_not_allowed stays out: it reports a client that
failed to strip PRF output, not a deployment refusing an authenticator.

The enrolment view now explains a refusal instead of showing the generic failure,
so a user told to reach for a security key can act on it.

Verified with npm run typecheck, npm test (315 passing), npm run lint,
npm run format:check, and npm run build.
@Bccorb
Bccorb merged commit fa27861 into main Aug 30, 2026
3 checks passed
@Bccorb
Bccorb deleted the feat/passkey-attachment branch August 30, 2026 22:01
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.

[Feature]: SDK cannot request a security key at enrollment

1 participant