Navigated to /docs/core/sessions-and-authentication

Sessions and authentication

Store opaque signed sessions, rotate credentials, protect authentication boundaries, and keep request identity isolated during SSR.

Create session storage

Tavo.js stores only a signed opaque session ID in the cookie. Session data stays in a SessionStore such as a database or Redis adapter. The built-in memory store is bounded but process-local, so reserve it for tests and local development.

  • cookie.name and at least one non-empty secret are required. Every secret must contain at least 32 UTF-8 bytes.

  • Cookie defaults are Path=/, HttpOnly enabled, and SameSite=Lax. Secure is inferred from an HTTPS request unless explicitly configured.

  • Secrets are checked in array order and new cookies are signed with the first secret. Put the new secret first and retain old secrets during a rotation window.

TS
tsimport "@tavojs/core/server-only";
import { createSessionStorage } from "@tavojs/core/server";

export const sessions = createSessionStorage<{ userId?: string }>({
  cookie: {
    name: "__session",
    secrets: [process.env.SESSION_SECRET!],
    maxAge: 60 * 60 * 24 * 7,
    sameSite: "lax"
  },
  store: databaseSessionStore
});

Session and storage API

  • getSession accepts a Request or an object with a request property. Missing, invalid, or expired cookies create a new empty session.

  • rotate replaces the opaque ID at the next commit and deletes the old store entry. Use it after login or a privilege change.

  • destroy marks the session for deletion. A later commit returns an expired cookie and removes the store entry.

  • redirect commits the session, appends Set-Cookie, normalizes the Location target, and defaults to status 303.

TS
tstype Session<T> = {
  readonly data: T;
  readonly id: string;
  readonly isNew: boolean;
  readonly rotated: boolean;
  readonly secure: boolean;
  get(key): T[key] | undefined;
  has(key): boolean;
  set(key, value): void;
  delete(key): void;
  rotate(): void;
  destroy(): void;
};

sessions.getSession(request): Promise<Session<T>>
sessions.commitSession(session, { maxAge? }): Promise<string>
sessions.destroySession(session, { maxAge? }): Promise<string>
sessions.redirect(to, session, init?): Promise<Response>

Custom and memory stores

  • The memory limit defaults to 10,000. Oldest entries are evicted when capacity is exceeded; expired entries are removed when read.

  • Set maxEntries to zero to disable persistence. A negative, infinite, or non-numeric limit throws.

  • commit maxAge overrides the cookie maxAge for that response. Expiry is stored alongside server data and serialized into the cookie.

  • Production stores must apply expiration consistently and support every runtime instance that can receive the user's next request.

TS
tstype SessionStore<T> = {
  get(id: string): MaybePromise<{ data: T; expiresAt: number | null } | null>;
  set(id: string, entry: { data: T; expiresAt: number | null }): MaybePromise<void>;
  delete(id: string): MaybePromise<void>;
};

const memory = createMemorySessionStore({ maxEntries: 10_000 });
memory.size();

Authenticate every request

  • Read authentication in server middleware, loaders, actions, or plugin handlers for every protected request.

  • After verifying login, rotate the session ID, set the user ID, and return sessions.redirect so the cookie is committed.

  • Send only safe profile fields into loader data. Never expose the session ID, cookie, signing secret, or access token.

  • A client auth store may mirror safe user data after hydration, but the server must still authorize each request from the session backend.

TS
tsexport const middleware = defineServerMiddleware(async ({ request }) => {
  const session = await sessions.getSession(request);
  if (!session.get("userId")) return { redirect: "/login", status: 302 };
});

export const load = defineServerLoader(async ({ request }) => {
  const session = await sessions.getSession(request);
  const user = await findUser(session.get("userId"));
  return { user: user ? { id: user.id, name: user.name } : null };
});

Look up exact public types

Follow linked API names to their canonical TypeScript declarations and package boundaries.