Skip to content

feat(clerk-js,shared,react): expose clientTrustState on SignInFuture - #10775

Open
tmilewski wants to merge 6 commits into
mainfrom
tom/device-trust-future
Open

tmilewski wants to merge 6 commits into
mainfrom
tom/device-trust-future

Conversation

@tmilewski

@tmilewski tmilewski commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Description

Custom sign-in flows built on SignInFuture (what useSignIn() returns in Core 3) can't read the Device Trust state of a sign-in. FAPI sends client_trust_state and the legacy SignIn resource parses it, but SignInFuture doesn't expose it. A customer asked for it so they can record, for each sign-in, whether Clerk recognized the device, and tell a trusted device apart from a sign-in that wasn't challenged.

This PR adds clientTrustState ('new', 'known' or 'pending') to SignInFuture, including through the @clerk/react state proxy. While the status is needs_client_trust, it reads 'new' even when FAPI leaves the field out, which FAPI does today for instances on that status. It's undefined when Device Trust is off, and before the first factor is verified. The SignIn snapshot now keeps the field too, so Expo restores it when FAPI is unreachable at startup.

https://github.com/clerk/clerk_go/pull/24016 makes FAPI return the field once the first factor is verified, in both Device Trust modes, and never before it. Until it ships, instances on the needs_client_trust status see 'new' during the challenge and undefined elsewhere. Merging this PR first is safe, but the JSDoc describes the behavior after the backend change, so the backend change should reach production first. The docs PR, https://github.com/clerk/clerk/pull/3821, updates the Device Trust guide and the legacy SignIn reference, and should merge after this change ships in a release.

The prebuilt components don't change. They read the legacy SignIn field, not SignInFuture. The new UI tests pin the "new device" notice. It renders once on each Device Trust screen whether or not FAPI sends the field, and stays hidden on the MFA screen.

The value reflects the device as of the latest response. Completing a sign-in makes the device trusted in most cases, so a completed sign-in usually reads 'known', even right after a challenge. FAPI doesn't return the value before the first factor, so a flow can't read it after create({ identifier }). That's deliberate: a value at that point would tell anyone who types an identifier whether that account has signed in on the browser. A flow reads it after the password, together with the status, which the docs PR explains.

The JSDoc on SignInFutureResource.clientTrustState is new public docs, so please check the wording.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

✎

@vercel

vercel Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
clerk-js-sandbox Ready Ready Preview Nov 11, 2026 9:48pm UTC
swingset Ready Ready Preview Nov 11, 2026 9:48pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 48d45e5

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

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

@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The Future sign-in resource now exposes clientTrustState, including a new fallback for the needs_client_trust status, and serializes the value in snapshots. The React sign-in proxy exposes the property. Tests and fixtures cover resource behavior, proxy values, device notices, and phone second-factor rendering.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: dmoerner, alexcarpenter

Merge Risk: Minimal · up to 48d45

This adds an optional read-only sign-in property, and existing behavior is unchanged. Whether the backend reports the field at the documented times depends on a separate FAPI change, which this review could not check. No concrete merge-blocking risk is evident.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the main change: it adds clientTrustState to SignInFuture across clerk-js, shared, and react. It matches the changeset, the SignIn resource change, the state proxy change, and the shar…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description states that the PR adds clientTrustState ('new', 'known', 'pending') to SignInFuture, including through the @clerk/react state proxy. It explains when the value is 'new' …
Full details: Title check

Explanation

The title names the main change: it adds clientTrustState to SignInFuture across clerk-js, shared, and react. It matches the changeset, the SignIn resource change, the state proxy change, and the shared type addition. The title follows conventional commit form and is specific.

Full details: Description check

Explanation

The description states that the PR adds clientTrustState ('new', 'known', 'pending') to SignInFuture, including through the @clerk/react state proxy. It explains when the value is 'new' and when it is undefined, and it says the SignIn snapshot keeps the field. It also covers the related backend and docs PRs and the new UI tests. This matches the changeset, the resource, proxy, shared type, and test changes.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Linked repositories: Some configured repositories could not be included in this review. Cross-repository analysis may be incomplete. Please check linked-repository access and settings.

Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 7d2dc32 and 48d45e5.

Files selected for processing: 2

  • .changeset/sign-in-future-client-trust-state.md
  • packages/shared/src/types/signInFuture.ts
Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

Included review availability: This review used your included allowance. 8 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 10 reviews per hour.

@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-11-11T21:50:19.303Z

Summary

Metric Count
Packages analyzed 20
Packages with changes 1
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 1

@clerk/shared

Current version: 4.44.1
Recommended bump: MINOR → 4.45.0

Subpath ./types

🟢 Additions (1)

Added: SignInFutureResource.clientTrustState
+ readonly clientTrustState?: ClientTrustState;

Added property SignInFutureResource.clientTrustState


Report generated by Break Check

Last ran on 48d45e5.

@pkg-pr-new

pkg-pr-new Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-biometrics

npm i https://pkg.pr.new/@clerk/expo-biometrics@10775

@clerk/expo-google-signin

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

@clerk/expo-native-components

npm i https://pkg.pr.new/@clerk/expo-native-components@10775

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/mosaic

npm i https://pkg.pr.new/@clerk/mosaic@10775

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 48d45e5

@blacksmith-sh

blacksmith-sh Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Matches useSignIn() exposes clientTrustState

After create() the value is undefined; after password() useSignIn() reads FAPI's known, pending or new, falls back to new on needs_client_trust without the field, and stays undefined with Device Trust off. Before, every cell read undefined.

Merge-base

Trust state per step

This PR 48d45e52

Trust state per step

Mocked FAPI responses

Also checked · 1 match · 1 not run

Skipped Prebuilt sign-in UI unchanged · Skipped
Matches SignIn snapshot keeps trust state

Proof on 48d45e52 (from 7d2dc324) · mocked: Clerk Frontend API (intercepted responses: Playwright route handlers return the environment, client...) · Did Proof get it right? Yes · No

View full Proof report · React 👎 or comment @codesmith dismiss this proof to dismiss

This branch was successfully deployed

2 active deployments
Preview – swingset — 48d45e52 Deployed Nov 11, 2026 by vercel[bot]
Preview – clerk-js-sandbox — 48d45e52 Deployed Nov 11, 2026 by vercel[bot]
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