fix: harden native session-minter token path - #9284
Conversation
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 detectedLatest commit: e94cb03 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 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.
|
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-google-signin
@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
No API Changes DetectedAll packages have stable APIs with no detected changes. Report generated by Break Check Last ran on |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
📝 WalkthroughWalkthroughThe Clerk JS session model now shares token freshness checks across JSON hydration and piggybacked client updates, preventing stale Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
Comment |
…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.
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.