Skip to main content

Default behavior

The client uses MemoryStorage by default. Tokens live in memory and are lost when the page reloads or the process exits. This works for quick prototypes and single-session CLI tools, but most production applications need persistent storage.

KontextStorage interface

Any object that implements getJson and setJson works as a storage backend:
interface KontextStorage {
  getJson<T>(key: string): Promise<T | undefined>;
  setJson<T>(key: string, value: T | undefined): Promise<void>;
}
Pass undefined as the value to delete a key.

localStorage adapter

For browser applications where tokens should survive page reloads:
const localStorageAdapter: KontextStorage = {
  async getJson<T>(key: string): Promise<T | undefined> {
    const raw = localStorage.getItem(key);
    return raw ? JSON.parse(raw) : undefined;
  },
  async setJson<T>(key: string, value: T | undefined): Promise<void> {
    if (value === undefined) {
      localStorage.removeItem(key);
    } else {
      localStorage.setItem(key, JSON.stringify(value));
    }
  },
};

const client = createKontextClient({
  clientId: "your-client-id",
  redirectUri: "http://localhost:3000/callback",
  storage: localStorageAdapter,
  onAuthRequired: (url) => {
    window.location.href = url.toString();
  },
});

sessionStorage adapter

Same pattern, scoped to the browser tab. Tokens are cleared when the tab closes:
const sessionStorageAdapter: KontextStorage = {
  async getJson<T>(key: string): Promise<T | undefined> {
    const raw = sessionStorage.getItem(key);
    return raw ? JSON.parse(raw) : undefined;
  },
  async setJson<T>(key: string, value: T | undefined): Promise<void> {
    if (value === undefined) {
      sessionStorage.removeItem(key);
    } else {
      sessionStorage.setItem(key, JSON.stringify(value));
    }
  },
};

Database-backed storage

For server-side applications that manage multiple users, store tokens in a database. This example uses a generic key-value table:
import { db } from "./db";

function createDbStorage(userId: string): KontextStorage {
  return {
    async getJson<T>(key: string): Promise<T | undefined> {
      const row = await db.kvStore.findUnique({
        where: { userId_key: { userId, key } },
      });
      return row?.value as T | undefined;
    },
    async setJson<T>(key: string, value: T | undefined): Promise<void> {
      if (value === undefined) {
        await db.kvStore.delete({
          where: { userId_key: { userId, key } },
        });
      } else {
        await db.kvStore.upsert({
          where: { userId_key: { userId, key } },
          create: { userId, key, value: value as any },
          update: { value: value as any },
        });
      }
    },
  };
}

const client = createKontextClient({
  clientId: "your-client-id",
  redirectUri: "https://myapp.com/callback",
  storage: createDbStorage(currentUser.id),
  onAuthRequired: (url) => {
    // server-side redirect
  },
});

Session keys

The sessionKey config option namespaces all storage keys. Use it when multiple users share the same storage backend but you want to keep their tokens separate.
const client = createKontextClient({
  clientId: "your-client-id",
  redirectUri: "https://myapp.com/callback",
  storage: localStorageAdapter,
  sessionKey: currentUser.id,
  onAuthRequired: (url) => {
    window.location.href = url.toString();
  },
});
With sessionKey set to "user-123", storage keys look like kontext:your-client-id:user-123:tokens. Without an explicit sessionKey, the SDK defaults to "default", producing keys like kontext:your-client-id:default:tokens. In hybrid mode (the default when no url is provided), the SDK appends a suffix per connection:
  • Gateway: kontext:your-client-id:default:gateway:tokens
  • Internal integrations: kontext:your-client-id:default:internal:<integrationId>:tokens
In single-endpoint mode (when url is set), the base format above is used as-is. This prevents token collisions when switching between users on the same device.

When to use custom storage

  • Browser SPA: Use localStorage so users stay signed in across page reloads.
  • Browser with multiple accounts: Use localStorage + sessionKey to isolate per user.
  • Server-side with multiple users: Use database storage with a user-scoped factory.
  • CLI tool: Default MemoryStorage is fine for single-session use. For persistent CLI auth, write to a file in the user’s config directory.
  • Testing: Default MemoryStorage works. Each test gets a fresh instance.

Next steps