feat(ui,clerk-js,shared): render the setup-passkey session task - #9610
Draft
dmoerner wants to merge 1 commit into
Draft
feat(ui,clerk-js,shared): render the setup-passkey session task#9610dmoerner wants to merge 1 commit into
dmoerner wants to merge 1 commit into
Conversation
Clerk's backend can now offer passkey enrolment right after sign-up, by
returning a `pending` session carrying a new `setup-passkey` task. No SDK
knows that key, and an unknown task key is not handled gracefully:
`getTaskEndpoint` builds `/tasks/undefined`, the SessionTasks router
matches no route, and the user is left on a pending session they cannot
clear — unable to use the app at all.
This adds the client half. A new TaskSetupPasskey card registers a passkey
through the existing `/v1/me/passkeys` endpoints, and `Session.skipTask`
declines the offer. `SUPPORTED_FAPI_VERSION` moves to 2026-08-20, which is
the version the backend gates the task behind.
1. `skipTask` is public API, not internal. For `setup-mfa` a headless flow
clears the task implicitly by calling the ordinary TOTP endpoints, but
an optional task has no side-effect equivalent — skipping is the only
way to clear it, so custom sign-up flows need it or their users get
stuck in `pending` permanently.
2. The instance chooses `off`, `optional` or `required` via
`passkey_settings.prompt_at_sign_up`, read from the environment. Only
an explicit `required` removes the decline button, so stale settings
can never trap a user in a task they cannot clear.
3. On a device with no platform authenticator, `optional` silently skips
the task and never renders the card. `required` cannot skip, so it
shows an explanatory dead end rather than a spinner.
4. Required mode uses its own subtitle. The offer framing ("Next time,
sign in with your fingerprint") reads as an invitation, which is
misleading on a step the user cannot decline.
Part of CORE-3729
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdvBsr6erhXVy4edPejNGx
🦋 Changeset detectedLatest commit: cea431f The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-google-signin
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Clerk's backend can now offer passkey enrolment right after sign-up by returning a
pendingsession carrying a newsetup-passkeytask. No SDK knows that key, and an unknown task key is not handled gracefully:getTaskEndpointbuilds/tasks/undefined, theSessionTasksswitch matches no route, andSessionTasksStartre-navigates to the same undefined path. The user is left on a pending session they cannot clear — unable to use the app at all.This adds the client half: a
TaskSetupPasskeycard that registers a passkey through the existing/v1/me/passkeysendpoints, andSession.skipTaskto decline the offer.Backend: clerk/clerk_go#21706. That must deploy first — this moves
SUPPORTED_FAPI_VERSIONto2026-08-20and the FAPI middleware rejects unknown API versions.Points worth review
skipTaskis public API. Forsetup-mfa, a headless flow clears the task implicitly by calling the ordinary TOTP endpoints. An optional task has no side-effect equivalent — skipping is the only way to clear it — so custom sign-up flows need this or their users are stuck inpendingpermanently. It was internal in the first draft; that was wrong.requiredremoves the decline button. The mode comes frompasskey_settings.prompt_at_sign_upon the environment. Absent, empty,offandoptionalall stay declinable, so stale settings degrade to a 400 on a skip attempt rather than trapping someone in an unclearable task.optional, the card silently skips and never renders — no point asking for a passkey the device cannot create. Inrequiredthere is no skip, so it shows an explanatory dead end with sign-out reachable, rather than a spinner. This is structural:requiredreturns before any skip is attempted.requiredgets its own subtitle. "Next time, sign in with your fingerprint" reads as an invitation, which is misleading on a step the user cannot decline. Both framings are pinned from both directions in tests so they cannot silently swap.Note for whoever ships passkey-as-2FA
satisfies_second_factordoes not exist anywhere in this repo — that feature's client types were never added, and it rides the same2026-08-20bump. More JS work outstanding there than the backend state suggests.Not changed
SUPPORTED_BAPI_VERSIONinpackages/backendis still2026-05-12, andhandshake.tssends it as__clerk_api_versionon the handshake redirect. Out of scope here, but it means handshake requests still declare the old FAPI version — worth a decision.Testing
9 tests in
TaskSetupPasskey.test.tsx(auto-skip without rendering, both refusal modes, cancelled WebAuthn dialog leaving both actions live, both copy framings) and 3 inSession.test.ts(endpoint URL, no body,currentTaskadvances, 400 propagates). Full suites pass: shared 1228, clerk-js 1060, ui 3200.tsc --noEmitclean; no new lint warnings.Part of CORE-3729
🤖 Generated with Claude Code
https://claude.ai/code/session_01VdvBsr6erhXVy4edPejNGx