Skip to content

fix: harden native session-minter token path - #9284

Draft
nikosdouvlis wants to merge 2 commits into
mainfrom
nikos/native-minter-hardening
Draft

fix: harden native session-minter token path#9284
nikosdouvlis wants to merge 2 commits into
mainfrom
nikos/native-minter-hardening

Conversation

@nikosdouvlis

Copy link
Copy Markdown
Member

Why

Native SDKs are getting Session Minter support: the app sends its previous session token to the edge as the seed the new token is minted from. That raises the stakes on the token clerk-js holds. A stale or regressed lastActiveToken is no longer just a stale local token, it becomes the input to the next mint, so staleness chains forward instead of self-correcting. This closes the Expo/native gaps that let that happen.

What changed

clerk-js: Client.fromJSON rebuilds every session object on a client update and used to adopt the payload's last_active_token unconditionally, so a piggybacked older token could regress the active session's token. It now carries the freshest of the prior instance's token and the incoming one through the same sid/org oiat guard the in-place path already uses, so a genuine session or org switch still adopts the incoming token while a stale piggyback cannot win. Clearing on a token-less payload is preserved.

expo: a failed initial load used to substitute dummy resources for both environment and client, wiping auth_config.session_minter for the whole instance lifetime; it now keeps a good cached environment when only the client cache is missing. The native 401 handler gets a short cooldown so a burst collapses to a single native-recovery cycle, and a rotated device token clears the cooldown so fresh native identity always gets a fresh attempt. tokenCache doc corrected to say it holds the client JWT.

Related

Part of a four-PR set adding native Session Minter support. This one covers clerk-js and Expo; the edge worker (cloudflare-workers), clerk-ios, and clerk-android land alongside it.

Native apps feed the previous session token to the edge Session Minter as the
mint seed, so a stale or regressed lastActiveToken is no longer just a stale
local token, it becomes the input to the next mint and the staleness chains
forward. Three gaps made that reachable on the Expo/native path.

clerk-js: Client.fromJSON rebuilds every session object on a client update, and
the rebuilt objects replaced the live ones while unconditionally adopting the
payload's last_active_token. A piggybacked response carrying an older token
could therefore regress the active session's token. fromJSON now carries the
freshest of the prior instance's token and the payload token, using the same
same-sid/same-org oiat guard that already protects the in-place path, so a
genuine session or org switch still adopts the incoming token while a stale
piggyback cannot win. Token-clearing on a token-less payload is preserved.

expo: a failed initial load substituted dummy resources for both environment
and client, wiping auth_config.session_minter for the whole instance lifetime
even when a good cached environment existed; it now substitutes only the
missing resource. The patched 401 handler ran full native-state recovery plus a
client refetch on every 401; a short cooldown collapses a burst to one cycle,
and a rotated device token clears the cooldown so fresh native identity always
gets a fresh attempt. tokenCache doc corrected to say it stores the client JWT.
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e94cb03

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

This PR includes changesets to release 4 packages
Name Type
@clerk/clerk-js Patch
@clerk/expo Patch
@clerk/chrome-extension Patch
@clerk/electron 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 Jul 30, 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 Jul 30, 2026 9:38am
swingset Ready Ready Preview Jul 30, 2026 9:38am

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-google-signin

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: e94cb03

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-30T09:38:23.701Z

Summary

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

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on e94cb03.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e43edc7-e4de-4e6b-abc5-52045cba94de

📥 Commits

Reviewing files that changed from the base of the PR and between e16252a and e94cb03.

📒 Files selected for processing (2)
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx
🔗 Linked repositories identified

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

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
  • clerk/cli (auto-detected)

📝 Walkthrough

Walkthrough

The Clerk JS session model now shares token freshness checks across JSON hydration and piggybacked client updates, preventing stale lastActiveToken values from replacing newer ones. Tests cover these cases. Expo now preserves cached environment data when client data is missing, throttles repeated native authentication recovery, resets recovery after device-token rotation, updates related tests, adds session_minter to dummy data, and corrects tokenCache documentation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: mikepitre, wobsoriano

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately highlights the main change: hardening the native session-minter token path.
Description check ✅ Passed The description directly matches the PR's changes to clerk-js, Expo recovery behavior, and tokenCache docs.
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.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks

Comment @coderabbitai help to get the list of available commands.

…e device token

The native 401 cooldown clears whenever the device-token cache changes, so a
fresh external identity gets a fresh recovery attempt. But a failed recovery
rolls the device token back to its previous value, and that rollback write fired
the same listener and cleared the cooldown, so a second 401 inside the window
re-ran full recovery, reopening the storm on exactly the failing-recovery path
the cooldown is meant to bound.

Route the rollback write on the 401 path through the notification suppression
recovery already uses for its own writes, so a rollback no longer clears the
cooldown. The native-client-event recovery path keeps notifying, since there the
rollback notification is load-bearing: it queues the native refresh that pushes
the restored token back to the native module.
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