diff --git a/.changeset/fresh-hats-notice.md b/.changeset/fresh-hats-notice.md
new file mode 100644
index 00000000000..df93e2c4066
--- /dev/null
+++ b/.changeset/fresh-hats-notice.md
@@ -0,0 +1,24 @@
+---
+'@clerk/clerk-expo': minor
+---
+
+Adds a secure token cache implementation using `expo-secure-store` which encrypts the session token before storing it.
+
+Usage:
+
+```tsx
+// app/_layout.tsx
+import { ClerkProvider } from '@clerk/clerk-expo'
+import { tokenCache } from '@clerk/clerk-expo/token-cache'
+
+export default function RootLayout() {
+ return (
+
+ {/* Your app code */}
+
+ )
+}
+```
\ No newline at end of file
diff --git a/.changeset/gentle-insects-glow.md b/.changeset/gentle-insects-glow.md
new file mode 100644
index 00000000000..2be48d963ae
--- /dev/null
+++ b/.changeset/gentle-insects-glow.md
@@ -0,0 +1,28 @@
+---
+'@clerk/clerk-expo': minor
+---
+
+Mark `secureStore` as deprecated in favor of `resourceCache` from `@clerk/clerk-expo/resource-cache`.
+
+Usage:
+
+```tsx
+// app/_layout.tsx
+import { ClerkProvider } from '@clerk/clerk-expo'
+import { tokenCache } from '@clerk/clerk-expo/token-cache'
+// import { secureStore } from '@clerk/clerk-expo/secure-store'
+import { resourceCache } from '@clerk/clerk-expo/resource-cache'
+
+export default function RootLayout() {
+ return (
+
+ {...}
+
+ )
+}
+```
diff --git a/packages/expo/package.json b/packages/expo/package.json
index e37ebce9b2d..0f3c891a0ae 100644
--- a/packages/expo/package.json
+++ b/packages/expo/package.json
@@ -43,6 +43,14 @@
"./secure-store": {
"types": "./dist/secure-store/index.d.ts",
"default": "./dist/secure-store/index.js"
+ },
+ "./token-cache": {
+ "types": "./dist/token-cache/index.d.ts",
+ "default": "./dist/token-cache/index.js"
+ },
+ "./resource-cache": {
+ "types": "./dist/resource-cache/index.d.ts",
+ "default": "./dist/resource-cache/index.js"
}
},
"main": "./dist/index.js",
@@ -53,7 +61,9 @@
"web",
"local-credentials",
"passkeys",
- "secure-store"
+ "secure-store",
+ "resource-cache",
+ "token-cache"
],
"scripts": {
"build": "tsup",
diff --git a/packages/expo/resource-cache/package.json b/packages/expo/resource-cache/package.json
new file mode 100644
index 00000000000..6a242788a1c
--- /dev/null
+++ b/packages/expo/resource-cache/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "../dist/resource-cache/index.js",
+ "types": "../dist/resource-cache/index.d.ts"
+}
diff --git a/packages/expo/src/secure-store/__tests__/dummy-test-data.ts b/packages/expo/src/resource-cache/__tests__/dummy-test-data.ts
similarity index 100%
rename from packages/expo/src/secure-store/__tests__/dummy-test-data.ts
rename to packages/expo/src/resource-cache/__tests__/dummy-test-data.ts
diff --git a/packages/expo/src/secure-store/__tests__/secure-store.test.ts b/packages/expo/src/resource-cache/__tests__/secure-store.test.ts
similarity index 93%
rename from packages/expo/src/secure-store/__tests__/secure-store.test.ts
rename to packages/expo/src/resource-cache/__tests__/secure-store.test.ts
index c8471fc8294..6904aa3d256 100644
--- a/packages/expo/src/secure-store/__tests__/secure-store.test.ts
+++ b/packages/expo/src/resource-cache/__tests__/secure-store.test.ts
@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
-import { createSecureStore } from '../secure-store';
+import { createResourceCacheStore } from '../resource-cache';
import { DUMMY_TEST_LARGE_JSON } from './dummy-test-data';
const KEY = 'key';
@@ -37,7 +37,7 @@ describe('SecureStore', () => {
beforeEach(() => {
vi.useFakeTimers();
- const createSecureStoreMock = () => {
+ const createResourceCacheStoreMock = () => {
const _map = new Map();
return {
setItemAsync: (key: string, value: string): Promise => {
@@ -53,7 +53,7 @@ describe('SecureStore', () => {
},
};
};
- const secureStoreMock = createSecureStoreMock();
+ const secureStoreMock = createResourceCacheStoreMock();
mocks.setItemAsync.mockImplementation(secureStoreMock.setItemAsync);
mocks.getItemAsync.mockImplementation(secureStoreMock.getItemAsync);
mocks.deleteItemAsync.mockImplementation(secureStoreMock.deleteItemAsync);
@@ -64,19 +64,19 @@ describe('SecureStore', () => {
});
test('sets a value correctly', async () => {
- const secureStore = createSecureStore();
+ const secureStore = createResourceCacheStore();
await secureStore.set(KEY, 'value');
await vi.runAllTimersAsync();
expect(await secureStore.get(KEY)).toBe('value');
});
test('returns null for a non-existent key', async () => {
- const secureStore = createSecureStore();
+ const secureStore = createResourceCacheStore();
expect(await secureStore.get(KEY)).toBeNull();
});
test('returns the last set value', async () => {
- const secureStore = createSecureStore();
+ const secureStore = createResourceCacheStore();
await secureStore.set(KEY, 'value1');
await secureStore.set(KEY, 'value2');
await vi.runAllTimersAsync();
@@ -90,7 +90,7 @@ describe('SecureStore', () => {
describe('delayed write', () => {
beforeEach(() => {
vi.useFakeTimers();
- const createSecureStoreMock = () => {
+ const createResourceCacheStoreMock = () => {
const _map = new Map();
return {
setItemAsync: (key: string, value: string): Promise => {
@@ -114,7 +114,7 @@ describe('SecureStore', () => {
},
};
};
- const secureStoreMock = createSecureStoreMock();
+ const secureStoreMock = createResourceCacheStoreMock();
mocks.setItemAsync.mockImplementation(secureStoreMock.setItemAsync);
mocks.getItemAsync.mockImplementation(secureStoreMock.getItemAsync);
mocks.deleteItemAsync.mockImplementation(secureStoreMock.deleteItemAsync);
@@ -125,7 +125,7 @@ describe('SecureStore', () => {
});
test('sets a value async', async () => {
- const secureStore = createSecureStore();
+ const secureStore = createResourceCacheStore();
void secureStore.set(KEY, 'value');
await vi.runAllTimersAsync();
const value = secureStore.get(KEY);
@@ -134,7 +134,7 @@ describe('SecureStore', () => {
});
test('sets the correct last value when many sets happen almost at the same time', async () => {
- const secureStore = createSecureStore();
+ const secureStore = createResourceCacheStore();
void secureStore.set(KEY, 'value');
void secureStore.set(KEY, 'value2');
void secureStore.set(KEY, 'value3');
@@ -177,7 +177,7 @@ describe('SecureStore', () => {
mocks.getItemAsync.mockImplementation(getItemAsync);
mocks.deleteItemAsync.mockImplementation(deleteItemAsync);
- const secureStore = createSecureStore();
+ const secureStore = createResourceCacheStore();
void secureStore.set(KEY, JSON.stringify(DUMMY_TEST_LARGE_JSON));
await vi.runAllTimersAsync();
@@ -225,7 +225,7 @@ describe('SecureStore', () => {
mocks.getItemAsync.mockImplementation(getItemAsync);
mocks.deleteItemAsync.mockImplementation(deleteItemAsync);
- const secureStore = createSecureStore();
+ const secureStore = createResourceCacheStore();
void secureStore.set(KEY, JSON.stringify(DUMMY_TEST_LARGE_JSON));
await vi.runAllTimersAsync();
void secureStore.set(KEY, 'new value');
@@ -288,7 +288,7 @@ describe('SecureStore', () => {
mocks.getItemAsync.mockImplementation(getItemAsync);
mocks.deleteItemAsync.mockImplementation(deleteItemAsync);
- const secureStore = createSecureStore();
+ const secureStore = createResourceCacheStore();
void secureStore.set(KEY, 'new value');
await vi.runAllTimersAsync();
const value = secureStore.get(KEY);
@@ -333,7 +333,7 @@ describe('SecureStore', () => {
mocks.getItemAsync.mockImplementation(getItemAsync);
mocks.deleteItemAsync.mockImplementation(deleteItemAsync);
- const secureStore = createSecureStore();
+ const secureStore = createResourceCacheStore();
void secureStore.set(KEY, 'new value');
await vi.runAllTimersAsync();
const value = secureStore.get(KEY);
diff --git a/packages/expo/src/resource-cache/index.ts b/packages/expo/src/resource-cache/index.ts
new file mode 100644
index 00000000000..6c7d59d6685
--- /dev/null
+++ b/packages/expo/src/resource-cache/index.ts
@@ -0,0 +1 @@
+export { createResourceCacheStore as resourceCache } from './resource-cache';
diff --git a/packages/expo/src/secure-store/secure-store.ts b/packages/expo/src/resource-cache/resource-cache.ts
similarity index 98%
rename from packages/expo/src/secure-store/secure-store.ts
rename to packages/expo/src/resource-cache/resource-cache.ts
index 0d99aa9cdb7..14009d3900e 100644
--- a/packages/expo/src/secure-store/secure-store.ts
+++ b/packages/expo/src/resource-cache/resource-cache.ts
@@ -27,7 +27,7 @@ type Metadata = {
* - key-{A/B}-complete -> 'true'/'false'
*
**/
-export const createSecureStore = (): IStorage => {
+export const createResourceCacheStore = (): IStorage => {
let queue: KeyValuePair[] = [];
let isProcessing = false;
diff --git a/packages/expo/src/secure-store/index.ts b/packages/expo/src/secure-store/index.ts
index 540c7a1b36d..4ae933093f6 100644
--- a/packages/expo/src/secure-store/index.ts
+++ b/packages/expo/src/secure-store/index.ts
@@ -1 +1,8 @@
-export { createSecureStore as secureStore } from './secure-store';
+import { resourceCache } from '../resource-cache';
+
+/**
+ * @deprecated Use `resourceCache` from `@clerk/clerk-expo/resource-cache` instead.
+ */
+const secureStore = resourceCache;
+
+export { secureStore };
diff --git a/packages/expo/src/token-cache/index.ts b/packages/expo/src/token-cache/index.ts
new file mode 100644
index 00000000000..4a67114e009
--- /dev/null
+++ b/packages/expo/src/token-cache/index.ts
@@ -0,0 +1,39 @@
+import * as SecureStore from 'expo-secure-store';
+
+import type { TokenCache } from '../cache';
+import { isNative } from '../utils';
+
+/**
+ * Create a token cache using Expo's SecureStore
+ */
+const createTokenCache = (): TokenCache => {
+ return {
+ getToken: async (key: string) => {
+ try {
+ const item = await SecureStore.getItemAsync(key);
+ return item;
+ } catch {
+ await SecureStore.deleteItemAsync(key);
+ return null;
+ }
+ },
+ saveToken: (key: string, token: string) => {
+ return SecureStore.setItemAsync(key, token);
+ },
+ };
+};
+
+/**
+ * Secure token cache implementation for Expo apps.
+ *
+ * Clerk stores the active user's session token in memory by default. In Expo apps, the
+ * recommended way to store sensitive data, such as tokens, is by using `expo-secure-store`
+ * which encrypts the data before storing it.
+ *
+ * To implement your own token cache, create an object that implements the `TokenCache` interface:
+ * - `getToken(key: string): Promise`
+ * - `saveToken(key: string, token: string): Promise`
+ *
+ * @type {TokenCache | undefined} Object with `getToken` and `saveToken` methods, undefined on web
+ */
+export const tokenCache = isNative() ? createTokenCache() : undefined;
diff --git a/packages/expo/token-cache/package.json b/packages/expo/token-cache/package.json
new file mode 100644
index 00000000000..cdcf5453a5e
--- /dev/null
+++ b/packages/expo/token-cache/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "../dist/token-cache/index.js",
+ "types": "../dist/token-cache/index.d.ts"
+}