Create new

Session Management

How sessions are created, stored, and revoked — and how to read the current user anywhere in your app.

Better Auth handles all session lifecycle automatically. This page covers how sessions work and how to interact with them in your code.


How sessions work

When a user signs in, Better Auth:

  1. Creates a session record in your database
  2. Sets a signed better-auth.session_token cookie
  3. Optionally caches session data in a compact cookie (cookieCache) to reduce DB reads

The session cookie is HttpOnly, Secure in production, and SameSite=Lax. You never handle it directly.

The cookieCache option in auth.ts stores a compact version of the session in a second cookie for up to 5 minutes. This means most requests don't hit the database at all:

session: {
  expiresIn: 60 * 60 * 24 * 2,  // 2 days
  updateAge: 60 * 60 * 24,       // extend expiry daily if active
  cookieCache: {
    enabled: true,
    maxAge: 5 * 60,              // serve from cookie for 5 minutes
    strategy: "compact",
  },
},

After 5 minutes the next request validates against the database and refreshes the cache.


Reading the session

Server Component

import { auth } from "@/lib/auth";
import { headers } from "next/headers";

const session = await auth.api.getSession({
  headers: await headers(),
});

// session is null if not authenticated
const user = session?.user;

Route handler

import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { NextResponse } from "next/server";

export async function GET() {
  const session = await auth.api.getSession({
    headers: await headers(),
  });

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  return NextResponse.json({ user: session.user });
}

Client Component

import { useSession } from "@/lib/auth-client";

export function ProfileButton() {
  const { data: session, isPending, error } = useSession();

  if (isPending) return <Skeleton className="h-8 w-8 rounded-full" />;
  if (!session) return <SignInButton />;

  return <Avatar user={session.user} />;
}

useSession uses SWR under the hood — it caches across components and re-validates when the window refocuses. You can call it in multiple components without extra requests.


Protecting routes

Protect entire route groups at the edge — the redirect happens before the page renders:

import { NextRequest, NextResponse } from "next/server";
import { getSessionCookie } from "better-auth/cookies";

export function middleware(request: NextRequest) {
  const session = getSessionCookie(request);

  const isProtected =
    request.nextUrl.pathname.startsWith("/dashboard") ||
    request.nextUrl.pathname.startsWith("/settings");

  if (isProtected && !session) {
    const url = request.nextUrl.clone();
    url.pathname = "/sign-in";
    url.searchParams.set("callbackUrl", request.nextUrl.pathname);
    return NextResponse.redirect(url);
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!api|_next|.*\\..*).*)"],
};

getSessionCookie reads the cookie but doesn't validate the signature — it's a fast edge check. Always call auth.api.getSession() in Server Components or route handlers when you need the verified user object.

Server Component guard

For pages that slip through middleware, or when you need the full user object to render the page:

import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

export default async function DashboardPage() {
  const session = await auth.api.getSession({ headers: await headers() });
  if (!session) redirect("/sign-in");

  return <Dashboard user={session.user} />;
}

Session data

The session object contains:

session.user = {
  id:                string
  name:              string
  email:             string
  emailVerified:     boolean
  image:             string | null
  createdAt:         Date
  updatedAt:         Date
  twoFactorEnabled:  boolean   // if twoFactor plugin is active
}

session.session = {
  id:          string
  userId:      string
  expiresAt:   Date
  createdAt:   Date
  updatedAt:   Date
  ipAddress:   string | null
  userAgent:   string | null
}

Listing active sessions

Show a user all their active sessions from the security settings page:

const { data: sessions } = await authClient.listSessions();

// sessions → Array of session objects with device info

Revoking sessions

// Revoke a specific session
await authClient.revokeSession({ token: session.session.id });

// Revoke all sessions except the current one
await authClient.revokeOtherSessions();

// Sign out (revokes current session)
await authClient.signOut();

Show "Sign out of all other devices" in security settings — users expect this when they think their account may have been compromised.