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

# Ave Session

> Persist OAuth tokens, refresh safely, and keep framework auth clients supplied with fresh ID tokens.

OAuth tokens expire on a short clock. That is separate from how long a user should stay signed in. When a registered Ave app requests `offline_access`, Ave issues a refresh token so your app can rotate tokens without sending the user through consent again.

`AveSession` gives you one supported lifecycle for that work: persist the token bundle, refresh before expiry, collapse duplicate refresh calls, broadcast changes across tabs, and save rotated refresh tokens atomically.

## When to use it

* You use a registered OAuth app and requested `offline_access`.
* You need a fresh `id_token` for Convex, your API, or another auth client.
* You want to avoid duplicated refresh calls, stale refresh tokens, and cold-start races.
* You need framework adapters for Convex, Svelte, Next.js, or Expo.

<Note>
  Quick Ave does not issue refresh tokens. Move to a registered app when you need long-lived sessions. See [Upgrading from Quick Ave](/guides/quick-ave-to-standard).
</Note>

## Disconnected apps

Disconnecting an app in Ave invalidates its refresh tokens and access to Ave APIs for the selected identity. Handle a definitive `invalid_grant` by clearing the app session and starting sign-in again. Reconnecting requires consent and does not reactivate old tokens.

An ID token or delegated resource JWT already accepted by another service remains subject to that service’s session policy and the token’s expiry. Signing out locally only clears the local session.

## Create a session

```ts theme={null}
import { AveSession, createLocalStorageAdapter } from "@ave-id/sdk";
import { completeOAuthCallback } from "@ave-id/sdk/client";

const session = new AveSession({
  oauth: {
    clientId: process.env.AVE_CLIENT_ID!,
    redirectUri: "https://yourapp.com/callback",
  },
  storage: createLocalStorageAdapter(),
  devtools: process.env.NODE_ENV === "development",
});

await completeOAuthCallback(session, {
  clientId: process.env.AVE_CLIENT_ID!,
  redirectUri: "https://yourapp.com/callback",
});

await session.hydrate();
const idToken = await session.getValidIdToken();
```

`completeOAuthCallback` runs `finishPkceLogin` and `setTokensFromResponse` together. Use it on your OAuth callback route, then call `hydrate()` during app startup.

If the redirect includes an E2EE `#app_key`, `finishPkceLogin` merges it into the token response and removes sensitive fragment parameters from the URL. `session.getAppKeyBase64()` returns the stored key after `hydrate()`.

<Tip>
  Set `devtools: true` only in development. It exposes `window.__aveSessionDev` with state, expiry helpers, and `lastRefreshError`.
</Tip>

`getValidIdToken()` waits for proactive refresh when the JWT is near expiry. For production refresh-token handling, prefer `createMemoryStorage()` or a backend-for-frontend instead of `localStorage`. See [Quickstart](/quickstart) for storage guidance.

## Convex

```ts theme={null}
import { ConvexReactClient } from "convex/react";
import { wireAveSessionToConvex } from "@ave-id/sdk/convex";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

await session.hydrate();
wireAveSessionToConvex(convex, session);
```

Call `session.setTokensFromResponse` after login. `wireAveSessionToConvex` passes a function that calls `getValidIdToken()` so Convex always receives a valid token.

## Next.js App Router

Use `AveSessionProvider` and `useAveSession` from `@ave-id/sdk/next`. If you use Convex, `AveConvexBridge` can replace a manual `wireAveSessionToConvex` call. See [Next.js App Router + Ave Session](/guides/nextjs-ave-session).

## Svelte

```ts theme={null}
import { aveSessionToStore } from "@ave-id/sdk/svelte";

const sessionStore = aveSessionToStore(session);
// sessionStore.subscribe(...) — status + snapshot
```

Hydrate in `onMount` before relying on authenticated UI.

## Expo

Native Expo has no Web Crypto for PKCE SHA-256. Configure `expo-crypto` once with `configureAveSdkForExpo` from `@ave-id/sdk/expo-session`, then use `AveSession` with `createSecureStoreAdapter` and `completeExpoOAuthCallback`. See [Expo with AuthSession](/guides/expo-auth-session).

```ts theme={null}
import * as ExpoCrypto from "expo-crypto";
import * as SecureStore from "expo-secure-store";
import * as WebBrowser from "expo-web-browser";
import {
  AveSession,
  completeExpoOAuthCallback,
  configureAveSdkForExpo,
  createSecureStoreAdapter,
  initExpoOAuthBrowserSession,
  onExpoAppForegroundRefresh,
  warmUpExpoAuthBrowser,
} from "@ave-id/sdk/expo-session";
import { AppState } from "react-native";

initExpoOAuthBrowserSession(WebBrowser);
configureAveSdkForExpo(ExpoCrypto);

const session = new AveSession({
  oauth: { clientId: "...", redirectUri: "myapp://callback" },
  storage: createSecureStoreAdapter(SecureStore),
  crossTabSync: false,
});

onExpoAppForegroundRefresh(AppState, session);

// Optional: in your login screen component
// useEffect(() => warmUpExpoAuthBrowser(WebBrowser), []);
```

## Backend refresh

Pass `customRefresh` in `AveSession` options to POST to your own `/api/session/refresh` endpoint. That endpoint can read an HttpOnly cookie and return `TokenResponse`-shaped JSON. The client never stores the refresh token.

## E2EE app key

For registered E2EE apps, Ave may append `#app_key=...` after consent. `finishPkceLogin` merges that value into the returned `TokenResponse`, and `AveSession` stores it as `appKeyBase64` next to your OAuth tokens so you can use `session.getAppKeyBase64()` after `hydrate()`.

You still own crypto policy. Import the key with Web Crypto according to [End-to-end encryption](/guides/end-to-end-encryption). FedCM flows may set `app_key` on the token response directly; that value is merged the same way.

## Dev tools

With `devtools: true`, `window.__aveSessionDev` exposes `getState()`, expiry helpers, and `lastRefreshError` in development builds.

## Server APIs

Use `getBearerToken` and `verifyAveIdTokenFromAuthHeader` from `@ave-id/sdk/server` to turn `Authorization: Bearer &lt;id_token&gt;` into `subject` plus claims. See [Postgres, SQLite, and JWT auth](/guides/sql-postgres-jwt-auth).

## Related

* [Convex custom auth](/guides/convex-custom-auth) — `applicationID`, which token to pass
* [Why sessions still fail](/guides/convex-custom-auth#sessions-still-flaky) — troubleshooting matrix
