From fd563c094379ad75ebf98db2d445611c618c7380 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 22 Sep 2026 13:25:38 -0700 Subject: [PATCH 1/3] fix(expo): key the offline resource cache by the full publishable key --- .../expo-resource-cache-key-per-instance.md | 5 ++ packages/expo/src/cache/ResourceCache.ts | 2 +- .../src/cache/__tests__/ResourceCache.test.ts | 48 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .changeset/expo-resource-cache-key-per-instance.md create mode 100644 packages/expo/src/cache/__tests__/ResourceCache.test.ts diff --git a/.changeset/expo-resource-cache-key-per-instance.md b/.changeset/expo-resource-cache-key-per-instance.md new file mode 100644 index 00000000000..2601503feb9 --- /dev/null +++ b/.changeset/expo-resource-cache-key-per-instance.md @@ -0,0 +1,5 @@ +--- +'@clerk/expo': patch +--- + +Fix the offline resource cache so an app that switches Clerk instances no longer starts offline with the previous instance's cached user and session. Existing caches are not carried over, so the first launch after upgrading fetches from the network. diff --git a/packages/expo/src/cache/ResourceCache.ts b/packages/expo/src/cache/ResourceCache.ts index ca5ddf8fb15..9ba8b693360 100644 --- a/packages/expo/src/cache/ResourceCache.ts +++ b/packages/expo/src/cache/ResourceCache.ts @@ -14,7 +14,7 @@ function createResourceCache(key: string): ResourceCache { if (!opts.storage || !opts.publishableKey) { throw new Error(`Clerk: ResourceCache for ${key} requires storage and publishableKey!`); } - itemKey = `${key}_${opts.publishableKey.slice(-5)}`; + itemKey = `${key}_${opts.publishableKey.replace(/=+$/, '')}`; storage = opts.storage(); }; diff --git a/packages/expo/src/cache/__tests__/ResourceCache.test.ts b/packages/expo/src/cache/__tests__/ResourceCache.test.ts new file mode 100644 index 00000000000..035282c0e80 --- /dev/null +++ b/packages/expo/src/cache/__tests__/ResourceCache.test.ts @@ -0,0 +1,48 @@ +import { beforeEach, describe, expect, test } from 'vitest'; + +import type { IStorage } from '../../provider/singleton/types'; +import { SessionJWTCache } from '../ResourceCache'; + +const store = new Map(); +const storage = (): IStorage => ({ + get: key => Promise.resolve(store.get(key) ?? null), + set: (key, value) => { + store.set(key, value); + return Promise.resolve(); + }, +}); + +const buildKey = (frontendApi: string) => `pk_test_${btoa(`${frontendApi}$`)}`; + +beforeEach(() => { + store.clear(); +}); + +describe('ResourceCache', () => { + test('isolates entries between instances whose publishable keys share a suffix', async () => { + const first = buildKey('a.clerk.accounts.dev'); + const second = buildKey('foo-bar-12.clerk.accounts.dev'); + expect(first.slice(-5)).toBe(second.slice(-5)); + + SessionJWTCache.init({ publishableKey: first, storage }); + await SessionJWTCache.save('jwt-for-first'); + + SessionJWTCache.init({ publishableKey: second, storage }); + await expect(SessionJWTCache.load()).resolves.toBeNull(); + + SessionJWTCache.init({ publishableKey: first, storage }); + await expect(SessionJWTCache.load()).resolves.toBe('jwt-for-first'); + }); + + test('uses storage keys that expo-secure-store accepts', async () => { + const padded = buildKey('ab.clerk.accounts.dev'); + expect(padded.endsWith('=')).toBe(true); + + SessionJWTCache.init({ publishableKey: padded, storage }); + await SessionJWTCache.save('jwt'); + + for (const storedKey of store.keys()) { + expect(storedKey).toMatch(/^[\w.-]+$/); + } + }); +}); From 904030245f359984c70bb557485c68122fdae9c5 Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Tue, 22 Sep 2026 13:31:08 -0700 Subject: [PATCH 2/3] chore: update changeset --- .changeset/expo-resource-cache-key-per-instance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/expo-resource-cache-key-per-instance.md b/.changeset/expo-resource-cache-key-per-instance.md index 2601503feb9..09f8f5784a5 100644 --- a/.changeset/expo-resource-cache-key-per-instance.md +++ b/.changeset/expo-resource-cache-key-per-instance.md @@ -2,4 +2,4 @@ '@clerk/expo': patch --- -Fix the offline resource cache so an app that switches Clerk instances no longer starts offline with the previous instance's cached user and session. Existing caches are not carried over, so the first launch after upgrading fetches from the network. +Fix the offline resource cache so an app that switches Clerk instances no longer starts offline with the previous instance's cached user and session. From a7659569d428c41bb3dafac775d0b2977d8a1d52 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 22 Sep 2026 14:19:46 -0700 Subject: [PATCH 3/3] fix(expo): strip publishable key padding without a regex --- packages/expo/src/cache/ResourceCache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/expo/src/cache/ResourceCache.ts b/packages/expo/src/cache/ResourceCache.ts index 9ba8b693360..b2b0b51e56f 100644 --- a/packages/expo/src/cache/ResourceCache.ts +++ b/packages/expo/src/cache/ResourceCache.ts @@ -14,7 +14,7 @@ function createResourceCache(key: string): ResourceCache { if (!opts.storage || !opts.publishableKey) { throw new Error(`Clerk: ResourceCache for ${key} requires storage and publishableKey!`); } - itemKey = `${key}_${opts.publishableKey.replace(/=+$/, '')}`; + itemKey = `${key}_${opts.publishableKey.split('=').join('')}`; storage = opts.storage(); };