From 2453740d206a362298dbffe3eb03ebec010adf4c Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 15 Jan 2026 07:47:49 -0600 Subject: [PATCH] fix(clerk-js): use JWT `iat` for token cache timing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `SessionTokenCache` used cache insertion time as the start of a token’s lifetime, even though the lifetime itself comes from `exp - iat`. Tokens added to the cache after issuance could therefore stay in the fresh/stale windows too long and be returned after expiry. Record the JWT iat as the cache start time once the token resolves, so stale-while-revalidate and expiry thresholds follow the token’s actual lifetime. (cherry picked from commit d5075a71dd3432075ca791a38ed57774abcee687 which was squashed and merged in 3ff86c42a27beccaf7d0a4af398ed71c66b21dbe). --- .changeset/token-cache-iat-core2.md | 5 +++++ packages/clerk-js/src/core/__tests__/tokenCache.test.ts | 4 ++-- .../clerk-js/src/core/resources/__tests__/Session.test.ts | 4 ++-- packages/clerk-js/src/core/tokenCache.ts | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 .changeset/token-cache-iat-core2.md diff --git a/.changeset/token-cache-iat-core2.md b/.changeset/token-cache-iat-core2.md new file mode 100644 index 00000000000..4d8d2a05f92 --- /dev/null +++ b/.changeset/token-cache-iat-core2.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Fix token cache stale-while-revalidate timing to use the JWT issued-at time, keeping refresh thresholds accurate when tokens are cached after issuance. diff --git a/packages/clerk-js/src/core/__tests__/tokenCache.test.ts b/packages/clerk-js/src/core/__tests__/tokenCache.test.ts index 52e69871800..95f50a41f1e 100644 --- a/packages/clerk-js/src/core/__tests__/tokenCache.test.ts +++ b/packages/clerk-js/src/core/__tests__/tokenCache.test.ts @@ -410,7 +410,7 @@ describe('SessionTokenCache', () => { it('removes token when it expires within the leeway threshold', async () => { const nowSeconds = Math.floor(Date.now() / 1000); - const iat = nowSeconds; + const iat = nowSeconds - 13; const exp = iat + 20; const soonJwt = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${btoa(JSON.stringify({ iat, exp }))}.signature`; @@ -419,7 +419,7 @@ describe('SessionTokenCache', () => { jwt: { claims: { exp, iat } }, } as any); - SessionTokenCache.set({ createdAt: nowSeconds - 13, tokenId: 'soon_expired_token', tokenResolver }); + SessionTokenCache.set({ createdAt: nowSeconds, tokenId: 'soon_expired_token', tokenResolver }); await tokenResolver; diff --git a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts index 9d0d183525a..f2b279fce59 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -21,9 +21,9 @@ describe('Session', () => { beforeEach(() => { // Mock Date.now() to make the test tokens appear valid // mockJwt has iat: 1666648250, exp: 1666648310 - // Set current time to 1666648260 (10 seconds after iat, 50 seconds before exp) + // Set current time to iat so token appears freshly issued (60 seconds before exp) vi.useFakeTimers(); - vi.setSystemTime(new Date(1666648260 * 1000)); + vi.setSystemTime(new Date(1666648250 * 1000)); }); afterEach(() => { diff --git a/packages/clerk-js/src/core/tokenCache.ts b/packages/clerk-js/src/core/tokenCache.ts index c95faf79cb5..5c68cf3c4aa 100644 --- a/packages/clerk-js/src/core/tokenCache.ts +++ b/packages/clerk-js/src/core/tokenCache.ts @@ -339,6 +339,7 @@ const MemoryTokenCache = (prefix = KEY_PREFIX): TokenCache => { const issuedAt = claims.iat; const expiresIn: Seconds = expiresAt - issuedAt; + value.createdAt = issuedAt; value.expiresIn = expiresIn; const timeoutId = setTimeout(deleteKey, expiresIn * 1000);