Skip to content

Commit 2eb8302

Browse files
fix: converge MRRT cache to single RT on concurrent refresh
1 parent c09dbf2 commit 2eb8302

4 files changed

Lines changed: 40 additions & 4 deletions

File tree

__tests__/Auth0Client/onlineAccess.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,8 @@ describe('Auth0Client', () => {
317317

318318
expect(updateEntrySpy).toHaveBeenCalledWith(
319319
TEST_REFRESH_TOKEN,
320-
'new_refresh_token'
320+
'new_refresh_token',
321+
undefined
321322
);
322323
});
323324
});

__tests__/cache/cache-manager.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,31 @@ cacheFactories.forEach(cacheFactory => {
10051005
expect(resultB?.refresh_token).toBe('different_refresh_token');
10061006
});
10071007

1008+
it('should update all entries unconditionally when useMrrt is true', async () => {
1009+
const entryA = {
1010+
...defaultData,
1011+
audience: 'https://api-a.com',
1012+
refresh_token: OLD_REFRESH_TOKEN
1013+
};
1014+
1015+
const entryB = {
1016+
...defaultData,
1017+
audience: 'https://api-b.com',
1018+
refresh_token: 'forked_refresh_token'
1019+
};
1020+
1021+
await manager.set(entryA);
1022+
await manager.set(entryB);
1023+
1024+
await manager.updateEntry(OLD_REFRESH_TOKEN, NEW_REFRESH_TOKEN, true);
1025+
1026+
const resultA = await manager.get(CacheKey.fromCacheEntry(entryA));
1027+
const resultB = await manager.get(CacheKey.fromCacheEntry(entryB));
1028+
1029+
expect(resultA?.refresh_token).toBe(NEW_REFRESH_TOKEN);
1030+
expect(resultB?.refresh_token).toBe(NEW_REFRESH_TOKEN);
1031+
});
1032+
10081033
it('should handle empty cache gracefully', async () => {
10091034
await expect(
10101035
manager.updateEntry(OLD_REFRESH_TOKEN, NEW_REFRESH_TOKEN)

src/Auth0Client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1509,7 +1509,8 @@ export class Auth0Client {
15091509
) {
15101510
await this.cacheManager.updateEntry(
15111511
cache.refresh_token,
1512-
tokenResult.refresh_token
1512+
tokenResult.refresh_token,
1513+
this.options.useMrrt
15131514
);
15141515
}
15151516

src/cache/cache-manager.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,18 +346,27 @@ export class CacheManager {
346346
}
347347

348348
/**
349-
* Updates the refresh token in all cache entries that contain the old refresh token.
349+
* Updates the refresh token in cache entries after a rotation.
350350
*
351351
* When a refresh token is rotated, multiple cache entries (for different audiences/scopes)
352352
* may share the same refresh token. This method propagates the new refresh token to all
353353
* matching entries.
354354
*
355+
* With MRRT enabled, concurrent `getTokenSilently()` calls for different audiences all
356+
* read the same RT and exchange it in parallel. Each exchange returns a different rotated
357+
* RT, which would leave the client with a diverged set of forked tokens per audience.
358+
* Passing `useMrrt = true` unconditionally overwrites every entry that has *any*
359+
* refresh token, so the last concurrent call to complete always wins and the client
360+
* converges to a single shared RT.
361+
*
355362
* @param oldRefreshToken The refresh token that was used and is now invalid
356363
* @param newRefreshToken The new refresh token received from the server
364+
* @param useMrrt When true, update all entries regardless of their current RT value
357365
*/
358366
async updateEntry(
359367
oldRefreshToken: string,
360368
newRefreshToken: string,
369+
useMrrt = false,
361370
): Promise<void> {
362371
const allKeys = await this.getCacheKeys();
363372

@@ -366,7 +375,7 @@ export class CacheManager {
366375
for (const key of allKeys) {
367376
const entry = await this.cache.get<WrappedCacheEntry>(key);
368377

369-
if (entry?.body?.refresh_token === oldRefreshToken) {
378+
if (entry?.body?.refresh_token && (useMrrt || entry.body.refresh_token === oldRefreshToken)) {
370379
entry.body.refresh_token = newRefreshToken;
371380
await this.cache.set(key, entry);
372381
}

0 commit comments

Comments
 (0)