feat(clerk-js,ui,shared,localizations): Support passkey as a second factor#9127
feat(clerk-js,ui,shared,localizations): Support passkey as a second factor#9127dmoerner wants to merge 3 commits into
Conversation
…actor
The Clerk backend can now offer passkey in a sign-in's (and session
reverification's) supported_second_factors when the instance allows
passkeys to satisfy the second factor and the user has a registered
passkey. This adds client support end to end:
- Types: passkey variants in SignInSecondFactor,
Prepare/AttemptSecondFactorParams, SessionVerificationSecondFactor,
and the session verify param unions.
- signIn.authenticateWithPasskey() detects needs_second_factor (and
needs_client_trust) and runs the prepare/attempt second-factor flow
against the in-progress sign-in instead of creating a new one; the
flow param is ignored there. attemptSecondFactor serializes the
WebAuthn credential like attemptFirstFactor. The future API gains
mfa.passkey().
- session.verifyWithPasskey({ level: 'second_factor' }) completes a
multi-factor step-up with a passkey.
- Prebuilt UI: passkey cards for the sign-in factor-two and
user-verification factor-two steps, offered via the existing
totp/phone-first ladder (passkey is only the starting factor when it
is the sole option), listed under "Use another method", and hidden
when WebAuthn is unsupported. Fixes the alternative-methods label
switch that threw on unknown second-factor strategies.
- New signIn.passkeyMfa / reverification.passkeyMfa localization keys.
Part of USER-3966
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 1e0fbd7 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.
|
📝 WalkthroughWalkthroughPasskey second-factor support is added to sign-in and session reverification APIs, shared types, prebuilt sign-in and user-verification UIs, tests, localization resources, and release metadata. ChangesPasskey second-factor support
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant PasskeyCard
participant ClerkResource
participant WebAuthn
User->>PasskeyCard: Select passkey second factor
PasskeyCard->>ClerkResource: prepare second-factor verification
ClerkResource-->>PasskeyCard: return passkey nonce
PasskeyCard->>WebAuthn: request credential
WebAuthn-->>PasskeyCard: return publicKeyCredential
PasskeyCard->>ClerkResource: attempt second-factor verification
ClerkResource-->>PasskeyCard: return verification response
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@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: |
API Changes Report
Summary
@clerk/sharedCurrent version: 4.25.2 Subpath
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx (1)
407-424: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winTest may pass for the wrong reason — wrap in
mockWebAuthnto test priority ordering.Without
mockWebAuthn,isWebAuthnSupported()likely returns false in the test environment, so passkey is excluded from candidacy regardless of priority sorting against TOTP. This test currently can't distinguish "TOTP wins on priority" from "passkey is filtered out due to no WebAuthn support." Wrapping this inmockWebAuthn(() => { ... })(as done for the sibling tests below) would ensure the sorting/priority logic is actually exercised.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx` around lines 407 - 424, Update the Passkey test “is never the starting factor when another second factor is available” to wrap its fixture setup, render, and assertions in mockWebAuthn, matching the sibling tests below. Keep supportTotp and supportPasskey enabled so the test validates priority ordering rather than passkey being excluded by unsupported WebAuthn.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts`:
- Around line 261-264: Restore direct static assignments in the passkey test
suites: update the authenticateWithPasskey and mfa.passkey cleanup to save the
original BaseResource._fetch and SignIn.clerk values before stubbing, then
reassign those originals in afterEach alongside vi.clearAllMocks() and
vi.unstubAllGlobals().
In `@packages/clerk-js/src/core/resources/Session.ts`:
- Around line 320-329: The verifyWithPasskey method silently treats invalid
levels as first-factor verification. Default level with nullish coalescing, then
explicitly validate that it is either 'first_factor' or 'second_factor' and
reject any other value before calling prepareFirstFactorVerification or
prepareSecondFactorVerification.
- Around line 320-329: Document the public verifyWithPasskey API with JSDoc
directly above the verifyWithPasskey method in Session, describing the
params.level options and default, the returned SessionVerificationResource, and
possible error behavior; flag the contract change for Docs-team review or
accompanying generated documentation updates.
In `@packages/clerk-js/src/core/resources/SignIn.ts`:
- Around line 575-610: Add or update JSDoc for the public
authenticateWithPasskey method to document that needs_second_factor and
needs_client_trust sign-ins reuse the in-progress sign-in via the second-factor
flow, and that the flow option is ignored in those states. Include the API
behavior change clearly and mark the documentation for Docs-team review.
In `@packages/ui/src/components/UserVerification/UVFactorTwoPasskeyCard.tsx`:
- Around line 32-41: Update handlePasskeysAttempt to guard against concurrent
passkey attempts: set the card’s loading state before calling verifyWithPasskey,
clear it in a finally handler regardless of success or failure, and disable the
passkey button while loading so additional clicks cannot abort the in-flight
ceremony.
---
Nitpick comments:
In `@packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx`:
- Around line 407-424: Update the Passkey test “is never the starting factor
when another second factor is available” to wrap its fixture setup, render, and
assertions in mockWebAuthn, matching the sibling tests below. Keep supportTotp
and supportPasskey enabled so the test validates priority ordering rather than
passkey being excluded by unsupported WebAuthn.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: c030f53b-d9cd-46b4-8a0f-e7f81cb887a2
📒 Files selected for processing (73)
.changeset/passkey-second-factor.mdpackages/clerk-js/src/core/resources/Session.tspackages/clerk-js/src/core/resources/SignIn.tspackages/clerk-js/src/core/resources/__tests__/Session.test.tspackages/clerk-js/src/core/resources/__tests__/SignIn.test.tspackages/localizations/src/ar-SA.tspackages/localizations/src/be-BY.tspackages/localizations/src/bg-BG.tspackages/localizations/src/bn-IN.tspackages/localizations/src/ca-ES.tspackages/localizations/src/cs-CZ.tspackages/localizations/src/da-DK.tspackages/localizations/src/de-DE.tspackages/localizations/src/el-GR.tspackages/localizations/src/en-GB.tspackages/localizations/src/en-US.tspackages/localizations/src/es-CR.tspackages/localizations/src/es-ES.tspackages/localizations/src/es-MX.tspackages/localizations/src/es-UY.tspackages/localizations/src/fa-IR.tspackages/localizations/src/fi-FI.tspackages/localizations/src/fr-FR.tspackages/localizations/src/he-IL.tspackages/localizations/src/hi-IN.tspackages/localizations/src/hr-HR.tspackages/localizations/src/hu-HU.tspackages/localizations/src/id-ID.tspackages/localizations/src/is-IS.tspackages/localizations/src/it-IT.tspackages/localizations/src/ja-JP.tspackages/localizations/src/kk-KZ.tspackages/localizations/src/ko-KR.tspackages/localizations/src/mn-MN.tspackages/localizations/src/ms-MY.tspackages/localizations/src/nb-NO.tspackages/localizations/src/nl-BE.tspackages/localizations/src/nl-NL.tspackages/localizations/src/pl-PL.tspackages/localizations/src/pt-BR.tspackages/localizations/src/pt-PT.tspackages/localizations/src/ro-RO.tspackages/localizations/src/ru-RU.tspackages/localizations/src/sk-SK.tspackages/localizations/src/sr-RS.tspackages/localizations/src/sv-SE.tspackages/localizations/src/ta-IN.tspackages/localizations/src/te-IN.tspackages/localizations/src/th-TH.tspackages/localizations/src/tr-TR.tspackages/localizations/src/uk-UA.tspackages/localizations/src/vi-VN.tspackages/localizations/src/zh-CN.tspackages/localizations/src/zh-TW.tspackages/shared/src/internal/clerk-js/errors.tspackages/shared/src/types/localization.tspackages/shared/src/types/session.tspackages/shared/src/types/sessionVerification.tspackages/shared/src/types/signInCommon.tspackages/shared/src/types/signInFuture.tspackages/ui/src/components/SignIn/SignInClientTrust.tsxpackages/ui/src/components/SignIn/SignInFactorTwo.tsxpackages/ui/src/components/SignIn/SignInFactorTwoAlternativeMethods.tsxpackages/ui/src/components/SignIn/SignInFactorTwoPasskeyCard.tsxpackages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsxpackages/ui/src/components/SignIn/shared.tspackages/ui/src/components/SignIn/utils.tspackages/ui/src/components/UserVerification/UVFactorTwoAlternativeMethods.tsxpackages/ui/src/components/UserVerification/UVFactorTwoPasskeyCard.tsxpackages/ui/src/components/UserVerification/UserVerificationFactorTwo.tsxpackages/ui/src/components/UserVerification/__tests__/UVFactorTwo.test.tsxpackages/ui/src/test/fixture-helpers.tspackages/ui/src/utils/factorSorting.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/shared/src/internal/clerk-js/errors.ts (1)
105-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the exported error helper.
The new exported function has an empty JSDoc block. Add accurate
@param,@returns,@throws, and@exampledocumentation describing the supported verification levels and caller-facing error. If this export is reference-facing, please also have the Docs team review the generated API content.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared/src/internal/clerk-js/errors.ts` around lines 105 - 112, Document the exported clerkInvalidVerificationLevel helper with complete JSDoc: describe functionName and level via `@param`, state the never return behavior via `@returns`, document the thrown caller-facing error via `@throws`, and add an `@example` showing an invalid level and the supported first_factor and second_factor values. Keep the documentation aligned with the existing error message and request Docs review for the generated API content if this export is reference-facing.Sources: Coding guidelines, Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/shared/src/internal/clerk-js/errors.ts`:
- Around line 105-112: Document the exported clerkInvalidVerificationLevel
helper with complete JSDoc: describe functionName and level via `@param`, state
the never return behavior via `@returns`, document the thrown caller-facing error
via `@throws`, and add an `@example` showing an invalid level and the supported
first_factor and second_factor values. Keep the documentation aligned with the
existing error message and request Docs review for the generated API content if
this export is reference-facing.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 9bc817b9-9d9f-4562-931c-26016589d189
📒 Files selected for processing (9)
.changeset/passkey-second-factor.mdpackages/clerk-js/src/core/resources/Session.tspackages/clerk-js/src/core/resources/SignIn.tspackages/clerk-js/src/core/resources/__tests__/Session.test.tspackages/clerk-js/src/core/resources/__tests__/SignIn.test.tspackages/react/src/stateProxy.tspackages/shared/src/internal/clerk-js/errors.tspackages/shared/src/types/session.tspackages/ui/src/components/UserVerification/UVFactorTwoPasskeyCard.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
- .changeset/passkey-second-factor.md
- packages/clerk-js/src/core/resources/tests/Session.test.ts
- packages/ui/src/components/UserVerification/UVFactorTwoPasskeyCard.tsx
- packages/clerk-js/src/core/resources/tests/SignIn.test.ts
- packages/shared/src/types/session.ts
- packages/clerk-js/src/core/resources/Session.ts
- packages/clerk-js/src/core/resources/SignIn.ts
If the environment variable on the backend is unset, the backend will not advertise passkeys for second factor. The javascript code, when on a new clerk js version that does support passkeys as second factor, was treating that as an error. Instead just fail gracefully, clients shouldn't be disrupted if the backend doesn't yet advertise support for something.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/clerk-js/src/core/resources/SignIn.ts (1)
594-619: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate second-factor passkey ceremony logic between
authenticateWithPasskeyandverifyMFAPasskey.Both blocks implement the same sequence (prepare second factor with
strategy: 'passkey'→ read/parse nonce → convert to public-key options → fetch credential → submit attempt), but diverge in error handling (clerkMissingWebAuthnPublicKeyOptions/rawthrow errorhere vs.ClerkRuntimeError/ClerkWebAuthnErrorthere). Extracting a shared private helper would prevent the two paths from drifting further as passkey second-factor logic evolves.Also applies to: 1563-1610
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/clerk-js/src/core/resources/SignIn.ts` around lines 594 - 619, Extract the duplicated second-factor passkey ceremony from authenticateWithPasskey and verifyMFAPasskey into a shared private helper on SignIn. Have both callers use the helper, preserving the existing prepareSecondFactor, nonce parsing, public-key option conversion, credential retrieval, and attemptSecondFactor flow while standardizing their error handling through the shared implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/clerk-js/src/core/resources/SignIn.ts`:
- Around line 594-619: Extract the duplicated second-factor passkey ceremony
from authenticateWithPasskey and verifyMFAPasskey into a shared private helper
on SignIn. Have both callers use the helper, preserving the existing
prepareSecondFactor, nonce parsing, public-key option conversion, credential
retrieval, and attemptSecondFactor flow while standardizing their error handling
through the shared implementation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 37a94a0a-db64-4faf-8786-49a7017aba5d
📒 Files selected for processing (4)
.changeset/passkey-second-factor.mdpackages/clerk-js/src/core/resources/SignIn.tspackages/clerk-js/src/core/resources/__tests__/SignIn.test.tspackages/shared/src/internal/clerk-js/errors.ts
💤 Files with no reviewable changes (1)
- packages/shared/src/internal/clerk-js/errors.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- .changeset/passkey-second-factor.md
The Clerk backend can now offer passkey in a sign-in's (and session reverification's) supported_second_factors when the instance allows passkeys to satisfy the second factor and the user has a registered passkey. This adds client support end to end:
Part of USER-3966
Flows tested successfully in local dev:
PASSKEY_SECOND_FACTOR_CLERKJS_VERSIONunset: Fallback to current behavior, no errors.Description
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
Summary by CodeRabbit