diff --git a/.changeset/empty-queens-float.md b/.changeset/empty-queens-float.md
new file mode 100644
index 000000000..7e862a352
--- /dev/null
+++ b/.changeset/empty-queens-float.md
@@ -0,0 +1,5 @@
+---
+
+## '@forgerock/effects': minor
+
+add token and local/session storage manager
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 4e2e60279..055887b22 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -5,7 +5,6 @@ import js from '@eslint/js';
 import packageJson from 'eslint-plugin-package-json/configs/recommended';
 import typescriptEslintEslintPlugin from '@typescript-eslint/eslint-plugin';
 import nxEslintPlugin from '@nx/eslint-plugin';
-import eslintPluginImport from 'eslint-plugin-import';
 import typescriptEslintParser from '@typescript-eslint/parser';
 
 const compat = new FlatCompat({
@@ -22,7 +21,6 @@ export default [
     plugins: {
       '@typescript-eslint': typescriptEslintEslintPlugin,
       '@nx': nxEslintPlugin,
-      import: eslintPluginImport,
     },
   },
   {
@@ -36,7 +34,6 @@ export default [
   },
   {
     rules: {
-      'import/extensions': [2, 'ignorePackages'],
       '@typescript-eslint/indent': ['error', 2],
       '@typescript-eslint/no-use-before-define': 'warn',
       'max-len': [
@@ -57,7 +54,19 @@ export default [
   {
     files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
     rules: {
-      'import/extensions': [2, 'ignorePackages'],
+      '@typescript-eslint/no-unused-vars': [
+        'error',
+        {
+          args: 'all',
+          argsIgnorePattern: '^_',
+          caughtErrors: 'all',
+          caughtErrorsIgnorePattern: '^_',
+          destructuredArrayIgnorePattern: '^_',
+          varsIgnorePattern: '^_',
+          ignoreRestSiblings: true,
+        },
+      ],
+
       '@nx/enforce-module-boundaries': [
         'warn',
         {
@@ -102,12 +111,6 @@ export default [
       files: ['**/*.ts', '**/*.tsx', '!**/*.spec.ts', '!**/*.test*.ts', '**/*.cts', '**/*.mts'],
       rules: {
         ...config.rules,
-        '@typescript-eslint/no-unused-vars': [
-          'error',
-          {
-            ignoreRestSiblings: true,
-          },
-        ],
       },
     })),
   ...compat
diff --git a/nx.json b/nx.json
index ca10a2e4d..dd14808f4 100644
--- a/nx.json
+++ b/nx.json
@@ -70,7 +70,7 @@
     },
     "test": {
       "inputs": ["default", "^default", "noMarkdown", "^noMarkdown"],
-      "dependsOn": ["^test", "^build"],
+      "dependsOn": ["^test", "^build", "^build", "^build"],
       "outputs": ["{projectRoot}/coverage"],
       "cache": true
     },
diff --git a/packages/device-client/eslint.config.mjs b/packages/device-client/eslint.config.mjs
index 7cbfa2e3e..0defbee6c 100644
--- a/packages/device-client/eslint.config.mjs
+++ b/packages/device-client/eslint.config.mjs
@@ -1,14 +1,5 @@
-import { FlatCompat } from '@eslint/eslintrc';
-import { dirname } from 'path';
-import { fileURLToPath } from 'url';
-import js from '@eslint/js';
 import baseConfig from '../../eslint.config.mjs';
 
-const compat = new FlatCompat({
-  baseDirectory: dirname(fileURLToPath(import.meta.url)),
-  recommendedConfig: js.configs.recommended,
-});
-
 export default [
   {
     ignores: ['**/dist'],
diff --git a/packages/device-client/package.json b/packages/device-client/package.json
index 4e12099c1..d9cdf9c0b 100644
--- a/packages/device-client/package.json
+++ b/packages/device-client/package.json
@@ -2,6 +2,11 @@
   "name": "@forgerock/device-client",
   "version": "0.0.1",
   "private": true,
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/ForgeRock/ping-javascript-sdk.git",
+    "directory": "packages/device-client"
+  },
   "sideEffects": false,
   "type": "module",
   "exports": {
diff --git a/packages/effects/src/lib/local-storage.test.ts b/packages/effects/src/lib/local-storage.test.ts
new file mode 100644
index 000000000..a93c3b659
--- /dev/null
+++ b/packages/effects/src/lib/local-storage.test.ts
@@ -0,0 +1,136 @@
+/**
+ *
+ *  Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ *
+ */
+import { describe, it, expect, beforeEach, afterEach, vi, Mock } from 'vitest';
+import {
+  getLocalStorageTokens,
+  setLocalStorageTokens,
+  removeTokensFromLocalStorage,
+  tokenFactory,
+} from './local-storage.js';
+import { TOKEN_ERRORS, type ConfigOptions, type Tokens } from '@forgerock/shared-types';
+
+describe('Token Storage Functions', () => {
+  // Mock config
+  const mockConfig: ConfigOptions = {
+    clientId: 'test-client',
+    prefix: 'test-prefix',
+  };
+
+  // Sample tokens
+  const sampleTokens: Tokens = {
+    accessToken: 'access-token-123',
+    idToken: 'id-token-456',
+    refreshToken: 'refresh-token-789',
+    tokenExpiry: 3600,
+  };
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+    const mockLocalStorage = {
+      getItem: vi.fn(),
+      setItem: vi.fn(),
+      removeItem: vi.fn(),
+    };
+    const mockSessionStorage = {
+      getItem: vi.fn(),
+      setItem: vi.fn(),
+      removeItem: vi.fn(),
+    };
+
+    vi.stubGlobal('localStorage', mockLocalStorage);
+    vi.stubGlobal('sessionStorage', mockSessionStorage);
+  });
+
+  afterEach(() => {
+    // Restore the original implementations after tests
+    vi.unstubAllGlobals();
+  });
+
+  describe('getLocalStorageTokens', () => {
+    it('should return undefined when no tokens exist', () => {
+      const tokens = getLocalStorageTokens(mockConfig);
+
+      expect(localStorage.getItem).toHaveBeenCalledWith('test-prefix-test-client');
+      expect(tokens).toEqual({
+        error: TOKEN_ERRORS.NO_TOKENS_FOUND_LOCAL_STORAGE,
+      });
+    });
+
+    it('should parse and return tokens when they exist', () => {
+      getLocalStorageTokens(mockConfig);
+      expect(localStorage.getItem).toHaveBeenCalledWith('test-prefix-test-client');
+    });
+
+    it('should return error object when tokens exist but cannot be parsed', () => {
+      const result = getLocalStorageTokens(mockConfig);
+
+      expect(localStorage.getItem).toHaveBeenCalledWith('test-prefix-test-client');
+      expect(result).toEqual({ error: TOKEN_ERRORS.NO_TOKENS_FOUND_LOCAL_STORAGE });
+    });
+  });
+
+  describe('setTokens', () => {
+    it('should stringify and store tokens in localStorage', () => {
+      setLocalStorageTokens(mockConfig, sampleTokens);
+
+      expect(localStorage.setItem).toHaveBeenCalledWith(
+        'test-prefix-test-client',
+        JSON.stringify(sampleTokens),
+      );
+    });
+  });
+
+  describe('removeTokens', () => {
+    it('should remove tokens from localStorage', () => {
+      removeTokensFromLocalStorage(mockConfig);
+
+      expect(localStorage.removeItem).toHaveBeenCalledWith('test-prefix-test-client');
+    });
+  });
+
+  describe('tokenFactory', () => {
+    it('should return an object with get, set, and remove methods', () => {
+      const tokenManager = tokenFactory(mockConfig);
+
+      expect(tokenManager).toHaveProperty('get');
+      expect(tokenManager).toHaveProperty('set');
+      expect(tokenManager).toHaveProperty('remove');
+      expect(typeof tokenManager.get).toBe('function');
+      expect(typeof tokenManager.set).toBe('function');
+      expect(typeof tokenManager.remove).toBe('function');
+    });
+
+    it('get method should retrieve tokens', () => {
+      (localStorage.getItem as Mock).mockReturnValueOnce(JSON.stringify(sampleTokens));
+
+      const tokenManager = tokenFactory(mockConfig);
+      const tokens = tokenManager.get();
+
+      expect(localStorage.getItem).toHaveBeenCalledWith('test-prefix-test-client');
+      expect(tokens).toEqual(sampleTokens);
+    });
+
+    it('set method should store tokens', () => {
+      const tokenManager = tokenFactory(mockConfig);
+      tokenManager.set(sampleTokens);
+
+      expect(localStorage.setItem).toHaveBeenCalledWith(
+        'test-prefix-test-client',
+        JSON.stringify(sampleTokens),
+      );
+    });
+
+    it('remove method should remove tokens', () => {
+      const tokenManager = tokenFactory(mockConfig);
+      tokenManager.remove();
+
+      expect(localStorage.removeItem).toHaveBeenCalledWith('test-prefix-test-client');
+    });
+  });
+});
diff --git a/packages/effects/src/lib/local-storage.ts b/packages/effects/src/lib/local-storage.ts
new file mode 100644
index 000000000..50f99e010
--- /dev/null
+++ b/packages/effects/src/lib/local-storage.ts
@@ -0,0 +1,43 @@
+/**
+ *
+ *  Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ *
+ */
+import { TOKEN_ERRORS, type ConfigOptions, type Tokens } from '@forgerock/shared-types';
+
+export function getLocalStorageTokens(Config: ConfigOptions) {
+  const tokenString = localStorage.getItem(`${Config.prefix}-${Config.clientId}`);
+  if (!tokenString) {
+    return {
+      error: TOKEN_ERRORS.NO_TOKENS_FOUND_LOCAL_STORAGE,
+    };
+  }
+  try {
+    const tokens = JSON.parse(tokenString) as Tokens;
+    return tokens;
+  } catch {
+    return {
+      error: TOKEN_ERRORS.PARSE_LOCAL_STORAGE,
+    };
+  }
+}
+
+export function setLocalStorageTokens(Config: ConfigOptions, tokens: Tokens) {
+  const tokenString = JSON.stringify(tokens);
+  localStorage.setItem(`${Config.prefix}-${Config.clientId}`, tokenString);
+}
+
+export function removeTokensFromLocalStorage(Config: ConfigOptions) {
+  localStorage.removeItem(`${Config.prefix}-${Config.clientId}`);
+}
+
+export function tokenFactory(config: ConfigOptions) {
+  return {
+    get: () => getLocalStorageTokens(config),
+    set: (tokens: Tokens) => setLocalStorageTokens(config, tokens),
+    remove: () => removeTokensFromLocalStorage(config),
+  };
+}
diff --git a/packages/effects/src/lib/request.mock.ts b/packages/effects/src/lib/request.mock.ts
index 814184723..f8b099325 100644
--- a/packages/effects/src/lib/request.mock.ts
+++ b/packages/effects/src/lib/request.mock.ts
@@ -98,7 +98,7 @@ const middleware: RequestMiddleware<ActionTypes>[] = [
   },
   // eslint-disable-next-line @typescript-eslint/ban-ts-comment
   // @ts-ignore
-  (req: ModifiedFetchArgs, action: Action, next: NextFn): void => {
+  (_req: ModifiedFetchArgs, action: Action, next: NextFn): void => {
     switch (action.type) {
       case mutateAction:
         action.type = 'hello' as ActionTypes;
diff --git a/packages/effects/src/lib/session-storage.test.ts b/packages/effects/src/lib/session-storage.test.ts
new file mode 100644
index 000000000..ef5c81765
--- /dev/null
+++ b/packages/effects/src/lib/session-storage.test.ts
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2025 Ping Identity Corporation.
+ * All rights reserved.
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
+import {
+  getSessionStorage,
+  setSessionStorage,
+  removeKeyFromSessionStorage,
+  sessionStorageFactory,
+} from './session-storage.js';
+import { TOKEN_ERRORS, type ConfigOptions, type Tokens } from '@forgerock/shared-types';
+
+// Create a mock sessionStorage object for tests
+const createMockSessionStorage = () => {
+  const sessionStorageMock = {
+    getItem: vi.fn((key: string) => sessionStorageMock.store[key] || null),
+    setItem: vi.fn((key: string, value: string) => {
+      sessionStorageMock.store[key] = value.toString();
+    }),
+    removeItem: vi.fn((key: string) => {
+      delete sessionStorageMock.store[key];
+    }),
+    clear: vi.fn(() => {
+      sessionStorageMock.store = {};
+    }),
+    store: {} as Record<string, string>,
+  };
+  return sessionStorageMock;
+};
+
+describe('Session Token Storage Functions', () => {
+  let sessionStorageMock: ReturnType<typeof createMockSessionStorage>;
+
+  // Mock config
+  const mockConfig: ConfigOptions = {
+    clientId: 'test-client',
+    prefix: 'test-prefix',
+  };
+
+  // Sample tokens
+  const sampleTokens: Tokens = {
+    accessToken: 'access-token-123',
+    idToken: 'id-token-456',
+    refreshToken: 'refresh-token-789',
+    tokenExpiry: 3600,
+  };
+
+  beforeEach(() => {
+    // Setup mock sessionStorage
+    sessionStorageMock = createMockSessionStorage();
+
+    // Mock the sessionStorage globally
+    vi.stubGlobal('sessionStorage', sessionStorageMock);
+
+    // Clear mock calls before each test
+    vi.clearAllMocks();
+  });
+
+  afterEach(() => {
+    // Restore the original implementations after tests
+    vi.unstubAllGlobals();
+  });
+
+  describe('getSessionStorage', () => {
+    it('should return undefined when no tokens exist', () => {
+      const tokens = getSessionStorage(mockConfig);
+
+      expect(sessionStorageMock.getItem).toHaveBeenCalledWith('test-prefix-test-client');
+      expect(tokens).toEqual({ error: TOKEN_ERRORS.NO_TOKENS_FOUND_SESSION_STORAGE });
+    });
+
+    it('should parse and return tokens when they exist', () => {
+      // This test will actually fail because there's a bug in the implementation
+      // The parsed tokens are not returned in the getSessionStorage function
+      sessionStorageMock.getItem.mockReturnValueOnce(JSON.stringify(sampleTokens));
+
+      const tokens = getSessionStorage(mockConfig);
+
+      expect(sessionStorageMock.getItem).toHaveBeenCalledWith('test-prefix-test-client');
+      // This will fail because of the bug, but we're keeping it to show the issue
+      expect(tokens).toEqual(sampleTokens);
+    });
+
+    it('should return error object when tokens exist but cannot be parsed', () => {
+      sessionStorageMock.getItem.mockReturnValueOnce('invalid-json');
+
+      const result = getSessionStorage(mockConfig);
+
+      expect(sessionStorageMock.getItem).toHaveBeenCalledWith('test-prefix-test-client');
+      expect(result).toEqual({ error: TOKEN_ERRORS.PARSE_SESSION_STORAGE });
+    });
+  });
+
+  describe('setSessionStorage', () => {
+    it('should stringify and store tokens in sessionStorage', () => {
+      setSessionStorage(mockConfig, sampleTokens);
+
+      expect(sessionStorageMock.setItem).toHaveBeenCalledWith(
+        'test-prefix-test-client',
+        JSON.stringify(sampleTokens),
+      );
+    });
+  });
+
+  describe('removeKeyFromSessionStorage', () => {
+    it('should remove tokens from sessionStorage', () => {
+      removeKeyFromSessionStorage(mockConfig);
+
+      expect(sessionStorageMock.removeItem).toHaveBeenCalledWith('test-prefix-test-client');
+    });
+  });
+
+  describe('sessionStorageFactory', () => {
+    it('should return an object with get, set, and remove methods', () => {
+      const tokenManager = sessionStorageFactory(mockConfig);
+
+      expect(tokenManager).toHaveProperty('get');
+      expect(tokenManager).toHaveProperty('set');
+      expect(tokenManager).toHaveProperty('remove');
+      expect(typeof tokenManager.get).toBe('function');
+      expect(typeof tokenManager.set).toBe('function');
+      expect(typeof tokenManager.remove).toBe('function');
+    });
+
+    it('get method should retrieve tokens', () => {
+      // This will also fail due to the bug in getSessionStorage
+      sessionStorageMock.getItem.mockReturnValueOnce(JSON.stringify(sampleTokens));
+
+      const tokenManager = sessionStorageFactory(mockConfig);
+      const tokens = tokenManager.get();
+
+      expect(sessionStorageMock.getItem).toHaveBeenCalledWith('test-prefix-test-client');
+      expect(tokens).toEqual(sampleTokens);
+    });
+
+    it('set method should store tokens', () => {
+      const tokenManager = sessionStorageFactory(mockConfig);
+      tokenManager.set(sampleTokens);
+
+      expect(sessionStorageMock.setItem).toHaveBeenCalledWith(
+        'test-prefix-test-client',
+        JSON.stringify(sampleTokens),
+      );
+    });
+
+    it('remove method should remove tokens', () => {
+      const tokenManager = sessionStorageFactory(mockConfig);
+      tokenManager.remove();
+
+      expect(sessionStorageMock.removeItem).toHaveBeenCalledWith('test-prefix-test-client');
+    });
+  });
+});
diff --git a/packages/effects/src/lib/session-storage.ts b/packages/effects/src/lib/session-storage.ts
new file mode 100644
index 000000000..109555989
--- /dev/null
+++ b/packages/effects/src/lib/session-storage.ts
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2025 Ping Identity Corporation.
+
+ * All rights reserved.
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+import type { ConfigOptions, Tokens } from '@forgerock/shared-types';
+import { TOKEN_ERRORS } from '@forgerock/shared-types';
+
+export function getSessionStorage(config: ConfigOptions) {
+  const tokenString = sessionStorage.getItem(`${config.prefix}-${config.clientId}`);
+  if (!tokenString) {
+    return {
+      error: TOKEN_ERRORS.NO_TOKENS_FOUND_SESSION_STORAGE,
+    };
+  }
+  try {
+    const tokens = JSON.parse(tokenString) as Tokens;
+    return tokens;
+  } catch {
+    return {
+      error: TOKEN_ERRORS.PARSE_SESSION_STORAGE,
+    };
+  }
+}
+
+export function setSessionStorage(config: ConfigOptions, tokens: Tokens) {
+  const tokenString = JSON.stringify(tokens);
+  sessionStorage.setItem(`${config.prefix}-${config.clientId}`, tokenString);
+}
+
+export function removeKeyFromSessionStorage(config: ConfigOptions) {
+  sessionStorage.removeItem(`${config.prefix}-${config.clientId}`);
+}
+
+export function sessionStorageFactory(config: ConfigOptions) {
+  return {
+    get: () => getSessionStorage(config),
+    set: (tokens: Tokens) => setSessionStorage(config, tokens),
+    remove: () => removeKeyFromSessionStorage(config),
+  };
+}
diff --git a/packages/effects/src/lib/token-storage.test.ts b/packages/effects/src/lib/token-storage.test.ts
new file mode 100644
index 000000000..459aac2b0
--- /dev/null
+++ b/packages/effects/src/lib/token-storage.test.ts
@@ -0,0 +1,172 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import {
+  TOKEN_ERRORS,
+  type ConfigOptions,
+  type TokenStoreObject,
+  type Tokens,
+} from '@forgerock/shared-types';
+import { getTokens, setTokens, removeTokens, tokenStorageFactory } from './token-storage.js';
+import * as sessionStorage from './session-storage.js';
+import * as localStorage from './local-storage.js';
+
+// Mock the storage modules
+vi.mock('./session-storage.js', () => ({
+  getSessionStorage: vi.fn(),
+  setSessionStorage: vi.fn(),
+  removeKeyFromSessionStorage: vi.fn(),
+}));
+
+vi.mock('./local-storage.js', () => ({
+  getLocalStorageTokens: vi.fn(),
+  setLocalStorageTokens: vi.fn(),
+  removeTokensFromLocalStorage: vi.fn(),
+}));
+
+describe('token-storage', () => {
+  const mockTokens: Tokens = { accessToken: 'test-access-token' };
+  const mockConfig: ConfigOptions = {
+    clientId: 'test-client',
+    tokenStore: 'localStorage' as const,
+  };
+
+  // Fix: Update mockTokenStore to match TokenStoreObject interface exactly
+  const mockTokenStore: TokenStoreObject = {
+    get: vi.fn().mockImplementation(async (_clientId: string) => mockTokens),
+    set: vi.fn().mockImplementation(async (_clientId: string, _tokens: Tokens) => undefined),
+    remove: vi.fn().mockImplementation(async (_clientId: string) => undefined),
+  };
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+  });
+
+  describe('getTokens', () => {
+    it('should get tokens from custom tokenStore when provided', async () => {
+      const result = await getTokens(mockConfig, mockTokenStore);
+      expect(result).toEqual(mockTokens);
+      expect(mockTokenStore.get).toHaveBeenCalledWith('test-client');
+    });
+
+    // Fix: Update configWithoutClientId to use correct type
+    it('should return error when clientId is missing with custom tokenStore', async () => {
+      const configWithoutClientId: ConfigOptions = {
+        tokenStore: {
+          get: mockTokenStore.get,
+          set: mockTokenStore.set,
+          remove: mockTokenStore.remove,
+        },
+      };
+      const result = await getTokens(configWithoutClientId, mockTokenStore);
+      expect(result).toEqual({ error: TOKEN_ERRORS.CLIENT_ID_REQUIRED });
+    });
+
+    it('should use localStorage by default', async () => {
+      const configWithoutStore: ConfigOptions = { clientId: 'test-client' };
+      await getTokens(configWithoutStore);
+      expect(localStorage.getLocalStorageTokens).toHaveBeenCalledWith(configWithoutStore);
+    });
+
+    it('should use sessionStorage when specified', async () => {
+      const sessionConfig: ConfigOptions = {
+        clientId: 'test-client',
+        tokenStore: 'sessionStorage',
+      };
+      await getTokens(sessionConfig);
+      expect(sessionStorage.getSessionStorage).toHaveBeenCalledWith(sessionConfig);
+    });
+  });
+
+  describe('setTokens', () => {
+    it('should set tokens in custom tokenStore', async () => {
+      const result = await setTokens(mockTokens, mockConfig, mockTokenStore);
+      expect(result).toBeUndefined();
+      expect(mockTokenStore.set).toHaveBeenCalledWith('test-client', mockTokens);
+    });
+
+    // Fix: Update configWithoutClientId to use correct type
+    it('should return error when clientId is missing with custom tokenStore', async () => {
+      const configWithoutClientId: ConfigOptions = {
+        tokenStore: {
+          get: mockTokenStore.get,
+          set: mockTokenStore.set,
+          remove: mockTokenStore.remove,
+        },
+      };
+      const result = await setTokens(mockTokens, configWithoutClientId, mockTokenStore);
+      expect(result).toEqual({ error: TOKEN_ERRORS.CLIENT_ID_REQUIRED });
+    });
+
+    it('should use localStorage when specified', async () => {
+      await setTokens(mockTokens, mockConfig);
+      expect(localStorage.setLocalStorageTokens).toHaveBeenCalledWith(mockConfig, mockTokens);
+    });
+
+    it('should use sessionStorage when specified', async () => {
+      const sessionConfig: ConfigOptions = {
+        clientId: 'test-client',
+        tokenStore: 'sessionStorage',
+      };
+      await setTokens(mockTokens, sessionConfig);
+      expect(sessionStorage.setSessionStorage).toHaveBeenCalledWith(sessionConfig, mockTokens);
+    });
+  });
+
+  describe('removeTokens', () => {
+    it('should remove tokens from custom tokenStore', async () => {
+      const result = await removeTokens(mockConfig, mockTokenStore);
+      expect(result).toBeUndefined();
+      expect(mockTokenStore.remove).toHaveBeenCalledWith('test-client');
+    });
+
+    // Fix: Update configWithoutClientId to use correct type
+    it('should return error when clientId is missing with custom tokenStore', async () => {
+      const configWithoutClientId: ConfigOptions = {
+        tokenStore: {
+          get: mockTokenStore.get,
+          set: mockTokenStore.set,
+          remove: mockTokenStore.remove,
+        },
+      };
+      const result = await removeTokens(configWithoutClientId, mockTokenStore);
+      expect(result).toEqual({ error: TOKEN_ERRORS.CLIENT_ID_REQUIRED });
+    });
+
+    it('should use localStorage when specified', async () => {
+      await removeTokens(mockConfig);
+      expect(localStorage.removeTokensFromLocalStorage).toHaveBeenCalledWith(mockConfig);
+    });
+
+    it('should use sessionStorage when specified', async () => {
+      const sessionConfig: ConfigOptions = {
+        clientId: 'test-client',
+        tokenStore: 'sessionStorage',
+      };
+      await removeTokens(sessionConfig);
+      expect(sessionStorage.removeKeyFromSessionStorage).toHaveBeenCalledWith(sessionConfig);
+    });
+
+    it('should return error for invalid token store type', async () => {
+      const invalidConfig: ConfigOptions = {
+        clientId: 'test-client',
+        // @ts-expect-error: Testing invalid tokenStore type
+        tokenStore: 'invalid',
+      };
+      const result = await removeTokens(invalidConfig);
+      expect(result).toEqual({
+        error: TOKEN_ERRORS.INVALID_STORE,
+      });
+    });
+  });
+
+  describe('tokenStorageFactory', () => {
+    it('should return an object with get, set, and remove methods', () => {
+      const tokenStorage = tokenStorageFactory(mockConfig);
+      expect(tokenStorage).toHaveProperty('get');
+      expect(tokenStorage).toHaveProperty('set');
+      expect(tokenStorage).toHaveProperty('remove');
+      expect(typeof tokenStorage.get).toBe('function');
+      expect(typeof tokenStorage.set).toBe('function');
+      expect(typeof tokenStorage.remove).toBe('function');
+    });
+  });
+});
diff --git a/packages/effects/src/lib/token-storage.ts b/packages/effects/src/lib/token-storage.ts
new file mode 100644
index 000000000..fd1b95d76
--- /dev/null
+++ b/packages/effects/src/lib/token-storage.ts
@@ -0,0 +1,128 @@
+import type { ConfigOptions, TokensError, TokenStoreObject } from '@forgerock/shared-types';
+import type { Tokens } from '@forgerock/shared-types';
+import { TOKEN_ERRORS } from '@forgerock/shared-types';
+
+import {
+  getSessionStorage,
+  removeKeyFromSessionStorage,
+  setSessionStorage,
+} from './session-storage.js';
+import {
+  getLocalStorageTokens,
+  removeTokensFromLocalStorage,
+  setLocalStorageTokens,
+} from './local-storage.js';
+
+export type TokenStoreType = 'localStorage' | 'sessionStorage' | 'custom';
+
+/**
+ * Gets tokens from the specified storage.
+ * @param config - Configuration options including clientId and storage type
+ * @param tokenStore - Optional custom token store implementation
+ * @returns Promise resolving to tokens or error
+ * @throws Never - Returns errors as objects instead
+ */
+export async function getTokens(
+  config: ConfigOptions,
+  tokenStore: TokenStoreObject,
+): Promise<Tokens | TokensError>;
+export async function getTokens(config: ConfigOptions): Promise<Tokens | TokensError>;
+export async function getTokens(
+  config: ConfigOptions,
+  maybeTokenStore?: TokenStoreObject,
+): Promise<Tokens | TokensError> {
+  if (maybeTokenStore) {
+    const clientId = config.clientId;
+    if (!clientId) {
+      return Promise.resolve({ error: 'Client ID is required.' });
+    }
+    return maybeTokenStore.get(clientId);
+  }
+
+  const tokenStore = config.tokenStore;
+
+  switch (tokenStore) {
+    case 'sessionStorage':
+      return getSessionStorage(config);
+    case 'localStorage':
+      return getLocalStorageTokens(config);
+    default:
+      return getLocalStorageTokens(config);
+  }
+}
+
+export function setTokens(tokens: Tokens, config: ConfigOptions): Promise<void>;
+export function setTokens(
+  tokens: Tokens,
+  config: ConfigOptions,
+  tokenStore: TokenStoreObject,
+): Promise<void | TokensError>;
+export async function setTokens(
+  tokens: Tokens,
+  config: ConfigOptions,
+  tokenStore?: TokenStoreObject,
+): Promise<void | TokensError> {
+  if (tokenStore) {
+    if (!config.clientId) {
+      return { error: 'Client ID is required.' };
+    }
+    await tokenStore.set(config.clientId, tokens);
+  } else {
+    // Enforce sync-compatible config at runtime
+    if (config.tokenStore !== 'localStorage' && config.tokenStore !== 'sessionStorage') {
+      return { error: TOKEN_ERRORS.INVALID_STORE };
+    }
+    // Wrap sync ops in Promise.resolve for consistency
+    if (config.tokenStore === 'sessionStorage') {
+      await Promise.resolve(setSessionStorage(config, tokens));
+    } else {
+      await Promise.resolve(setLocalStorageTokens(config, tokens));
+    }
+  }
+}
+/**
+ * Removes stored tokens.
+ */
+export async function removeTokens(config: ConfigOptions): Promise<void | TokensError>;
+export async function removeTokens(
+  config: ConfigOptions,
+  tokenStore: TokenStoreObject,
+): Promise<void | TokensError>;
+export async function removeTokens(
+  config: ConfigOptions,
+  tokenStore?: TokenStoreObject,
+): Promise<void | TokensError> {
+  if (!tokenStore) {
+    if (config.tokenStore === 'sessionStorage') {
+      return await removeKeyFromSessionStorage(config);
+    } else if (config.tokenStore === 'localStorage') {
+      return await removeTokensFromLocalStorage(config);
+    } else {
+      return Promise.resolve({
+        error: TOKEN_ERRORS.INVALID_STORE,
+      });
+    }
+  } else {
+    if (!config.clientId) {
+      return Promise.resolve({
+        error: TOKEN_ERRORS.CLIENT_ID_REQUIRED,
+      });
+    }
+    return await tokenStore.remove(config.clientId);
+  }
+}
+
+export function tokenStorageFactory(config: ConfigOptions): TokenStoreObject {
+  return {
+    get: async (clientId: string) => getTokens({ ...config, clientId }),
+    set: async (clientId: string, tokens: Tokens) => setTokens(tokens, { ...config, clientId }),
+    remove: async (clientId: string) => removeTokens({ ...config, clientId }),
+  };
+}
+
+// Default export for backward compatibility
+export default {
+  get: getTokens,
+  set: setTokens,
+  remove: removeTokens,
+};
diff --git a/packages/shared-types/package.json b/packages/shared-types/package.json
index 5bdc9a7ba..c3bd71c39 100644
--- a/packages/shared-types/package.json
+++ b/packages/shared-types/package.json
@@ -2,6 +2,11 @@
   "name": "@forgerock/shared-types",
   "version": "0.0.1",
   "private": true,
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/ForgeRock/ping-javascript-sdk.git",
+    "directory": "packages/shared-types"
+  },
   "type": "module",
   "exports": {
     ".": {
diff --git a/packages/shared-types/src/lib/config.types.ts b/packages/shared-types/src/lib/config.types.ts
index ef411616f..0925d6eeb 100644
--- a/packages/shared-types/src/lib/config.types.ts
+++ b/packages/shared-types/src/lib/config.types.ts
@@ -1,6 +1,6 @@
 import { Callback } from './callback.types.js';
 import { RequestMiddleware } from './shared-types.js';
-import { Tokens } from './tokens.types.js';
+import { TokenStoreObject } from './tokens.types.js';
 
 /**
  * Async ConfigOptions for well-known endpoint usage
@@ -39,15 +39,6 @@ export interface AsyncServerConfig extends Omit<ServerConfig, 'baseUrl'> {
   wellknown?: string;
 }
 
-/**
- * API for implementing a custom token store
- */
-export interface TokenStoreObject {
-  get: (clientId: string) => Promise<Tokens>;
-  set: (clientId: string, token: Tokens) => Promise<void>;
-  remove: (clientId: string) => Promise<void>;
-}
-
 /**
  * Configuration options with a server configuration specified.
  */
diff --git a/packages/shared-types/src/lib/tokens.types.ts b/packages/shared-types/src/lib/tokens.types.ts
index 4d5bf8ab6..c3b25afe7 100644
--- a/packages/shared-types/src/lib/tokens.types.ts
+++ b/packages/shared-types/src/lib/tokens.types.ts
@@ -4,3 +4,29 @@ export interface Tokens {
   refreshToken?: string;
   tokenExpiry?: number;
 }
+
+export interface TokensError {
+  error: TOKEN_ERRORS;
+}
+
+export const TOKEN_ERRORS = {
+  CLIENT_ID_REQUIRED: 'Client ID is required.',
+  INVALID_STORE: 'Invalid token store type. Expected "local storage" or "sessionStorage".',
+  STORAGE_REQUIRED:
+    'Local storage or session storage is required when not passing in a custom store',
+  PARSE_LOCAL_STORAGE: 'Could not parse token from local storage',
+  PARSE_SESSION_STORAGE: 'Could not parse token from session storage',
+  NO_TOKENS_FOUND_SESSION_STORAGE: `No tokens found in session storage`,
+  NO_TOKENS_FOUND_LOCAL_STORAGE: `No tokens found in local storage`,
+} as const;
+
+export type TOKEN_ERRORS = (typeof TOKEN_ERRORS)[keyof typeof TOKEN_ERRORS];
+
+/**
+ * API for implementing a custom token store
+ */
+export interface TokenStoreObject {
+  get: (clientId: string) => Promise<Tokens | TokensError>;
+  set: (clientId: string, token: Tokens) => Promise<void>;
+  remove: (clientId: string) => Promise<void | TokensError>;
+}