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 操作 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

注册 for free to join this conversation on GitHub. Already have an account? 登录 to comment

项目

None yet

Development

Successfully merging this pull request may close these issues.

1 participant