Skip to content

feat(ui,clerk-js,shared): render the setup-passkey session task - #9610

Draft
dmoerner wants to merge 1 commit into
mainfrom
feat/setup-passkey-task
Draft

feat(ui,clerk-js,shared): render the setup-passkey session task#9610
dmoerner wants to merge 1 commit into
mainfrom
feat/setup-passkey-task

Conversation

@dmoerner

Copy link
Copy Markdown
Contributor

Drafted by Claude Code, from a design discussion with @danielmoerner. Reviewers should treat the reasoning below as a proposal to check, not a summary of settled decisions.

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 switch matches no route, and SessionTasksStart re-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 TaskSetupPasskey card that registers a passkey through the existing /v1/me/passkeys endpoints, and Session.skipTask to decline the offer.

Backend: clerk/clerk_go#21706. That must deploy first — this moves SUPPORTED_FAPI_VERSION to 2026-08-20 and the FAPI middleware rejects unknown API versions.

Points worth review

  1. skipTask is public API. For setup-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 in pending permanently. It was internal in the first draft; that was wrong.
  2. Only an explicit required removes the decline button. The mode comes from passkey_settings.prompt_at_sign_up on the environment. Absent, empty, off and optional all stay declinable, so stale settings degrade to a 400 on a skip attempt rather than trapping someone in an unclearable task.
  3. Unsupported devices differ by mode. In optional, the card silently skips and never renders — no point asking for a passkey the device cannot create. In required there is no skip, so it shows an explanatory dead end with sign-out reachable, rather than a spinner. This is structural: required returns before any skip is attempted.
  4. required gets 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_factor does not exist anywhere in this repo — that feature's client types were never added, and it rides the same 2026-08-20 bump. More JS work outstanding there than the backend state suggests.

Not changed

SUPPORTED_BAPI_VERSION in packages/backend is still 2026-05-12, and handshake.ts sends it as __clerk_api_version on 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 in Session.test.ts (endpoint URL, no body, currentTask advances, 400 propagates). Full suites pass: shared 1228, clerk-js 1060, ui 3200. tsc --noEmit clean; no new lint warnings.

Part of CORE-3729

🤖 Generated with Claude Code

https://claude.ai/code/session_01VdvBsr6erhXVy4edPejNGx

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-bot

changeset-bot Bot commented Aug 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cea431f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@clerk/localizations Minor
@clerk/clerk-js Minor
@clerk/shared Minor
@clerk/ui Minor
@clerk/react Patch
@clerk/chrome-extension Patch
@clerk/electron Patch
@clerk/expo Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/expo-passkeys Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/headless Patch
@clerk/hono Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/vue Patch
@clerk/swingset Patch

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

@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 29, 2026 12:48am
swingset Ready Ready Preview Aug 29, 2026 12:48am

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Aug 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9610

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9610

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9610

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9610

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9610

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9610

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9610

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9610

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9610

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9610

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9610

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9610

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9610

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9610

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9610

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9610

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9610

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9610

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9610

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9610

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9610

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9610

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9610

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9610

commit: cea431f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant