better-auth
Use when implementing authentication with Better Auth — session management, route protection, user roles, email/password auth, social OAuth, or database adapters. Also use when choosing a TypeScript-first auth library with full control over session strategy.
| Model | Source |
|---|---|
| sonnet | pack: auth |
Full Reference
┏━ 🔧 better-auth ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ TypeScript-first auth — sessions, OAuth, roles ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
better-auth
Section titled “better-auth”TypeScript-first authentication framework — session management, route protection, roles, OAuth, and database adapters.
Quick Reference
Section titled “Quick Reference”| Item | Value |
|---|---|
| Package | better-auth v1.x |
| Companion | @better-fetch/fetch |
| Auth catch-all route | /api/auth/[...all]/route.ts |
| Session endpoint | GET /api/auth/get-session |
| Session via | Signed cookies (httpOnly) |
| Default session expiry | 86400s (1 day) |
| Cookie cache max-age | 300s (5 min) — skips DB lookups |
| Roles | "user" and "admin" (lowercase) |
Reference Index
Section titled “Reference Index”| Doc | What’s inside |
|---|---|
| reference/setup.md | Server config, client config, Drizzle adapter, catch-all route (Next.js, Hono, Express) |
| reference/sessions.md | Session lifecycle, cookie cache, middleware, useSession, server-side checks |
| reference/providers.md | Email/password, social OAuth (Google, GitHub, etc.), magic links |
| reference/roles.md | Role system, admin plugin, RBAC patterns, RBAC scoping |
Common Operations
Section titled “Common Operations”Setup server config (lib/auth.ts):
import { betterAuth } from "better-auth";import { drizzleAdapter } from "better-auth/adapters/drizzle";import { admin } from "better-auth/plugins";
export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema }), emailAndPassword: { enabled: true }, plugins: [admin()],});Protect an API route:
import { auth } from "@/lib/auth";import { headers } from "next/headers";
const session = await auth.api.getSession({ headers: await headers() });if (!session) return Response.json({ error: "Unauthorized" }, { status: 401 });// session.user.id, session.user.role, session.user.emailClient: reactive session:
"use client";import { useSession } from "@/lib/auth-client";const { data: session, isPending } = useSession();Client: sign in / sign out:
import { signIn, signOut } from "@/lib/auth-client";await signIn.email({ email, password, callbackURL: "/dashboard" });await signOut({ fetchOptions: { onSuccess: () => router.push("/login") } });Check role:
const isAdmin = session.user.role === "admin"; // always lowercaseCommon Mistakes
Section titled “Common Mistakes”| Mistake | Fix |
|---|---|
Role as "ADMIN" / "SUPER_ADMIN" | Roles are lowercase: "user" and "admin" |
Forgetting await headers() | Must await headers() before passing to getSession() |
Using auth.api.getSession() in middleware | Middleware (Edge) uses betterFetch("/api/auth/get-session", ...) |
Skipping callbackURL on signIn.email() | Without it, post-login redirect may not work |
session.user.isAdmin field | No such field — check session.user.role === "admin" |
GET /api/auth/get-session rate limited | Exclude it: customRules: { "/get-session": false } |