Skip to content

fix: converge MRRT cache to single RT on concurrent refresh#1702

Open
yogeshchoudhary147 wants to merge 1 commit into
mainfrom
fix/mrrt-concurrent-refresh-token-divergence
Open

fix: converge MRRT cache to single RT on concurrent refresh#1702
yogeshchoudhary147 wants to merge 1 commit into
mainfrom
fix/mrrt-concurrent-refresh-token-divergence

Conversation

@yogeshchoudhary147

@yogeshchoudhary147 yogeshchoudhary147 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Problem

When MRRT (multi-resource refresh tokens) is enabled, concurrent getTokenSilently() calls for different audiences can leave the cache in a diverged state.

Steps to reproduce:

  1. Configure Auth0Client with useRefreshTokens: true and multiple resource audiences.
  2. Fire two concurrent getTokenSilently() calls for different audiences (e.g. api-1 and api-2) before either resolves.
  3. Both calls read the same RT (RT0) from cache and exchange it in parallel at /oauth/token.
  4. The server issues a different rotated RT to each call (RT1-a to Call 1, RT1-b to Call 2).
  5. Call 1 completes first:
    • updateEntry(RT0, RT1-a) propagates RT1-a to all MRRT entries, including api-2.
    • cacheManager.set writes api-1's own entry with RT1-a.
  6. Call 2 completes:
    • updateEntry(RT0, RT1-b) finds no entry with RT0 (already rotated to RT1-a), so it is a no-op. RT1-b is never propagated to api-1.
    • cacheManager.set writes api-2's own entry with RT1-b.
  7. Final state: api-1 has RT1-a, api-2 has RT1-b. The cache is diverged.

Root cause: Each call does two writes — updateEntry propagates the new RT to other MRRT entries, and cacheManager.set writes the caller's own entry. With concurrent calls, whichever call finishes second has its updateEntry silently no-op (the old RT is already gone), so the new RT never propagates to the other audience entries. Both calls end up writing different RTs to their own entries leaving the cache in a diverged state.

Fix

  • Added a useMrrt parameter to CacheManager.updateEntry. When true, instead of matching oldRefreshToken, it unconditionally overwrites every entry that holds a RT. This way the last concurrent call to complete always wins and the client converges to a single shared RT.
  • Added a clientId parameter to CacheManager.updateEntry. Without this, the unconditional overwrite would also update RT-bearing entries for other Auth0Client instances sharing the same storage (e.g. localStorage). The loop now skips any key whose parsed clientId does not match the caller's.
  • Passes this.options.clientId and this.options.useMrrt at the call site in Auth0Client.

Test plan

  • npx jest __tests__/cache/cache-manager.test.ts - new tests verify (1) useMrrt=true overwrites a forked RT unconditionally, (2) entries belonging to a different clientId are left untouched, and (3) entries evicted between key listing and fetching are skipped gracefully
  • npx jest __tests__/Auth0Client/onlineAccess.test.ts - existing spy assertion updated to reflect the new arguments
  • npm test - full unit suite green

@yogeshchoudhary147
yogeshchoudhary147 requested a review from a team as a code owner July 26, 2026 03:18
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

MRRT refresh-token rotation now scopes cache updates by client ID and passes an MRRT flag to enable replacement across applicable entries. Tests cover MRRT propagation, audience isolation, evicted entries, and the updated method signature.

Changes

MRRT refresh-token rotation

Layer / File(s) Summary
Conditional cache update behavior
src/cache/cache-manager.ts, __tests__/cache/cache-manager.test.ts
updateEntry accepts a client ID and optional useMrrt flag, scopes updates to the client, and supports MRRT replacement across entries with differing stored tokens. Tests cover matching tokens, MRRT behavior, audience isolation, evicted entries, and cache preservation.
Auth0Client rotation integration
src/Auth0Client.ts, __tests__/Auth0Client/onlineAccess.test.ts
Rotated-token propagation passes the client ID and MRRT setting to updateEntry, and the related assertion expects the expanded argument list.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the MRRT cache convergence fix during concurrent refreshes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mrrt-concurrent-refresh-token-divergence

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
__tests__/Auth0Client/onlineAccess.test.ts (1)

320-321: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the enabled-MRRT forwarding path.

This assertion only verifies useMrrt === undefined. Add an integration case configured with useMrrt: true and assert that updateEntry receives true; otherwise a regression in Auth0Client forwarding could remain undetected.

🤖 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 `@__tests__/Auth0Client/onlineAccess.test.ts` around lines 320 - 321, Add an
integration test in the online access flow using configuration with useMrrt:
true, then assert updateEntry receives true for that argument. Keep the existing
undefined-case coverage and use the same Auth0Client test setup and updateEntry
assertion path to verify enabled-MRRT forwarding.
🤖 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 `@src/cache/cache-manager.ts`:
- Around line 358-360: Restrict the unconditional MRRT update in the cache
update flow to entries belonging to the current clientId. Before applying the
useMrrt=true update to keys returned by getCacheKeys() or cache.allKeys(),
filter out keys associated with other client IDs while preserving updates for
the current client.
- Around line 355-360: Serialize the complete MRRT backfill loop, including all
per-key reads and writes, so concurrent getTokenSilently flows cannot interleave
updates. Add or reuse a shared mutex around the loop that performs the useMrrt
backfill in the cache manager, or replace it with an atomic batch update, while
preserving convergence to one shared rotated refresh token.

---

Nitpick comments:
In `@__tests__/Auth0Client/onlineAccess.test.ts`:
- Around line 320-321: Add an integration test in the online access flow using
configuration with useMrrt: true, then assert updateEntry receives true for that
argument. Keep the existing undefined-case coverage and use the same Auth0Client
test setup and updateEntry assertion path to verify enabled-MRRT forwarding.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0dcc4f42-63e8-4097-9c66-e62105fd6147

📥 Commits

Reviewing files that changed from the base of the PR and between c09dbf2 and 2eb8302.

📒 Files selected for processing (4)
  • __tests__/Auth0Client/onlineAccess.test.ts
  • __tests__/cache/cache-manager.test.ts
  • src/Auth0Client.ts
  • src/cache/cache-manager.ts

Comment thread src/cache/cache-manager.ts
Comment thread src/cache/cache-manager.ts
@yogeshchoudhary147
yogeshchoudhary147 force-pushed the fix/mrrt-concurrent-refresh-token-divergence branch from 2eb8302 to f15b2a8 Compare July 26, 2026 04:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
__tests__/cache/cache-manager.test.ts (1)

1008-1031: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Test concurrent convergence, not only single-call overwrite.

This test proves MRRT replacement across audiences, but it cannot catch interleaving two refreshes across multiple keys. Add a deterministic fake-cache/barrier test that runs two MRRT updates concurrently and asserts all entries for TEST_CLIENT_ID converge to one token. The PR objective explicitly targets convergence during concurrent getTokenSilently() calls.

🤖 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 `@__tests__/cache/cache-manager.test.ts` around lines 1008 - 1031, Extend the
cache-manager tests around updateEntry to add a deterministic fake-cache/barrier
scenario that starts two concurrent MRRT updates for TEST_CLIENT_ID across
multiple audience entries. Coordinate the interleaving explicitly, then assert
every matching entry converges to a single refresh token, covering concurrent
getTokenSilently-style updates rather than only sequential overwrite behavior.
🤖 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 `@__tests__/cache/cache-manager.test.ts`:
- Around line 1008-1031: Extend the cache-manager tests around updateEntry to
add a deterministic fake-cache/barrier scenario that starts two concurrent MRRT
updates for TEST_CLIENT_ID across multiple audience entries. Coordinate the
interleaving explicitly, then assert every matching entry converges to a single
refresh token, covering concurrent getTokenSilently-style updates rather than
only sequential overwrite behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cb2ae7b9-d266-47e5-b6c2-2afa0cf3f2c3

📥 Commits

Reviewing files that changed from the base of the PR and between 2eb8302 and f15b2a8.

📒 Files selected for processing (4)
  • __tests__/Auth0Client/onlineAccess.test.ts
  • __tests__/cache/cache-manager.test.ts
  • src/Auth0Client.ts
  • src/cache/cache-manager.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/Auth0Client/onlineAccess.test.ts
  • src/Auth0Client.ts

@yogeshchoudhary147
yogeshchoudhary147 force-pushed the fix/mrrt-concurrent-refresh-token-divergence branch from f15b2a8 to b8e173b Compare July 26, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant