> ## Documentation Index
> Fetch the complete documentation index at: https://www.commercengine.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Token Management

> Complete guide to authentication and token management in the TypeScript SDK

Commerce Engine has three practical states:

| State | Created by | Typical capabilities |
| - | - | - |
| Public request | Public accessor + Storefront API key | Products, categories, search, recommendations, store configuration |
| Anonymous session | Session accessor ensures a token | Cart, wishlist where enabled, analytics continuity, checkout preparation |
| Logged-in session | OTP/password authentication | Account, saved addresses, loyalty, orders, authenticated checkout |

Public catalog browsing does not require an anonymous session. Create a live session only when the customer begins a session-bound flow or when the application intentionally bootstraps one at startup.

## Browser SPA

Use `BrowserTokenStorage` inside the `session` configuration of `createStorefront()`.

```typescript lib/storefront.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import {
  BrowserTokenStorage,
  Environment,
  createStorefront,
} from "@commercengine/storefront";

const tokenStorage = new BrowserTokenStorage("brand_");

export const storefront = createStorefront({
  storeId: import.meta.env.VITE_STORE_ID,
  apiKey: import.meta.env.VITE_API_KEY,
  environment: Environment.Staging,
  session: {
    tokenStorage,
  },
});

export const publicSdk = storefront.public();
export const sdk = storefront.session();
```

Establish the session eagerly once when live session features should be immediately available:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
await sdk.ensureAccessToken();
```

Do not scatter `ensureAccessToken()` before every cart, customer, or order call. Managed session methods can ensure or refresh the session internally.

## First-party SSR frameworks

Next.js, TanStack Start, Astro, and SvelteKit wrappers use matching browser and request-cookie storage.

The shared model is:

* `publicStorefront()` never reads or writes session tokens
* `clientStorefront()` uses the browser-side cookie-backed session
* `serverStorefront()` uses the current request's cookies
* `bootstrap()` creates the first anonymous browser session when no cookies exist
* returning users hydrate from existing cookies on the first request

<Info>
  First-party wrappers use cookies that the browser SDK can read and write, so they are not HttpOnly. This is intentional for a client-managed storefront session. If your security architecture requires HttpOnly tokens, place Commerce Engine behind a backend-for-frontend and own the token lifecycle there instead of using the browser session client.
</Info>

### First visit

On the first visit there are no session cookies. Mount the framework bootstrap once near the root:

```tsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
useEffect(() => {
  storefront.bootstrap().catch(console.error);
}, []);
```

The call is deduplicated and becomes a no-op for returning users whose session already exists.

### Returning visit

Existing cookies are available to request-bound server accessors immediately. A returning user's account or cart can therefore render on the first request where the framework supports request-time rendering.

## Peek versus ensure

The session helper exposes passive and active reads.

### Peek

Peek methods read current state without creating a session or refreshing tokens. They return `null` when unavailable.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const accessToken = await sdk.session.peekAccessToken();
const refreshToken = await sdk.session.peekRefreshToken();
const userInfo = await sdk.session.peekUserInfo();
const userId = await sdk.session.peekUserId();
```

Use peek when inspecting current state or passing existing tokens into Hosted Checkout initialization.

### Ensure

Ensure methods may create an anonymous session or refresh an expired token.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const accessToken = await sdk.session.ensureAccessToken();
const userId = await sdk.session.ensureUserId();
const customerId = await sdk.session.ensureCustomerId();
```

Use ensure only when valid session state is required before continuing.

## Authentication transition

OTP or password login returns a new, more privileged token pair. With managed session storage, the SDK stores those tokens and invokes the token-update callback.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const { data: otp, error: loginError } = await sdk.auth.loginWithEmail({
  email: "customer@example.com",
  register_if_not_exists: true,
});

if (loginError || !otp) throw new Error(loginError?.message ?? "OTP could not be sent");

const { data: verified, error: verifyError } = await sdk.auth.verifyOtp({
  otp: "123456",
  otp_token: otp.otp_token,
  otp_action: otp.otp_action,
});

if (verifyError) throw new Error(verifyError.message);
```

The active anonymous cart is merged into the logged-in account by Commerce Engine. Do not create a replacement cart after login.

## Logout continuity

Logout does not end the Commerce Engine session completely. It replaces the logged-in tokens with a new anonymous token pair tied to the continuing user journey.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const { data, error } = await sdk.auth.logout();

if (error) {
  console.error(error.message);
}
```

With managed storage, allow the SDK to store the returned anonymous tokens. **Do not call `clearTokens()` after a successful logout**, because that discards the new anonymous session and can break cart and analytics continuity.

Update the UI from the resulting user/token state:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const user = await sdk.getUserInfo();
const loggedIn = await sdk.isLoggedIn();
```

Use `clearTokens()` only for an unrecoverable local session, explicit account removal, or a security response where continuity should be discarded.

## Refresh and recovery

Managed session mode refreshes access tokens when required. Application code should normally respond to the final `{ data, error }` result rather than implementing a second refresh loop around every call.

When both access and refresh tokens are invalid, a session-bound method may need to establish a new anonymous session. Treat the resulting state as anonymous and require login again for protected account operations.

Do not log token values or expose them through error monitoring.

## Parameterless session overloads

Many session-scoped SDK methods can resolve `user_id` or `customer_id` from the active token.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const { data: cart } = await sdk.cart.getUserCart();
const { data: addresses } = await sdk.customer.listAddresses();
const { data: orders } = await sdk.order.listOrders();
const { data: wishlist } = await sdk.cart.getWishlist();
```

Pass an explicit identity only when the operation contract requires it or when a trusted server process is intentionally operating on behalf of another identity.

Auth profile methods may still require an explicit user ID:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const userId = await sdk.getUserId();
if (!userId) throw new Error("No active user");

const { data } = await sdk.auth.getUserDetails({ id: userId });
```

## Hosted Checkout session synchronization

When `@commercengine/storefront` and `@commercengine/checkout` are both present, there must be one session owner. The Storefront SDK owns the session and Hosted Checkout runs in `provided` mode.

Two-way synchronization is required because either side may refresh tokens or complete login/logout.

### SDK to checkout

For a browser SPA, configure the callback inside `session`:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { getCheckout } from "@commercengine/checkout";

session: {
  tokenStorage,
  onTokensUpdated: (accessToken, refreshToken) => {
    getCheckout().updateTokens(accessToken, refreshToken);
  },
}
```

For first-party SSR wrappers, use the wrapper's top-level `onTokensUpdated` option as shown in its framework guide.

### Checkout to SDK

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
initCheckout({
  storeId,
  apiKey,
  authMode: "provided",
  accessToken: (await sdk.getAccessToken()) ?? undefined,
  refreshToken: (await sdk.session.peekRefreshToken()) ?? undefined,
  onTokensUpdated: ({ accessToken, refreshToken }) => {
    void sdk.setTokens(accessToken, refreshToken);
  },
});
```

<Warning>
  The callback signatures differ: the Storefront SDK callback receives two arguments; Hosted Checkout receives one object. Mixing them silently breaks synchronization.
</Warning>

## Server-side isolation

Never share one mutable session SDK with `MemoryTokenStorage` across concurrent end users. Create a request-bound SDK or storage adapter so one user's token cannot be observed by another request.

Public SDK instances, which carry no user session, can generally be shared.

See [Node.js](/docs/sdk/nodejs-integration) for server patterns.

## Security checklist

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
[ ] publishable Storefront key is not confused with an admin secret
[ ] private admin, webhook, and payment credentials remain server-only
[ ] session storage matches the runtime
[ ] one mutable token store is not shared across users
[ ] tokens are never logged
[ ] public rendering does not create sessions
[ ] successful logout preserves the returned anonymous tokens
[ ] Hosted Checkout and Storefront SDK use one synchronized session
[ ] unrecoverable session failure returns the UI to an anonymous or login-required state
```

<Card title="Framework session guides" icon="key" href="/docs/sdk/nextjs-integration">
  Use the framework integration pages for exact bootstrap and request-cookie patterns.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.