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:
- Creates a
sessionrecord in your database - Sets a signed
better-auth.session_tokencookie - 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.
Cookie cache
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
Middleware (recommended)
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.
Auth, billing, orgs, and emails — all wired up. Clone and deploy in minutes.
Get launch.now