Authentication is Better Auth configured in src/modules/auth/auth.ts — the single source of truth for providers, sessions, and hooks. Three sign-in methods ship enabled:
- Email + password (with email verification sent on signup).
- Google OAuth — registers itself only when
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETare set; the UI hides the button otherwise. - Magic links — "Email me a sign-in link" on the sign-in page.
Reading the session
In server code (pages, layouts, route handlers):
import { getSession, requireSession } from "@/modules/auth";
const session = await getSession(); // Session | null
const session = await requireSession(); // redirects to /sign-in if absentrequireSession() in a layout protects an entire subtree — that's how /dashboard is guarded. In client components, use the hooks from @/modules/auth/auth-client (useSession, signIn, signOut).
Two-layer protection
src/proxy.ts(Next 16's middleware) does a fast, optimistic cookie check and redirects obvious anonymous visitors. The real validation — hitting the database — runs in the layout. Don't move authorization into the proxy; Next.js explicitly recommends against it.
Signup side effects
New users get a 50-credit bonus and a welcome email through Better Auth's databaseHooks.user.create.after — see it in auth.ts. That hook is the right place for any "on signup" behavior (analytics, default workspace rows), keeping it out of the UI flow. The email is fire-and-forget: delivery failures are logged, never surfaced as signup errors.
Account settings
/dashboard/settings ships the screens every SaaS needs: rename, change password (revokes other sessions), change email (confirmed by email), and GDPR-grade account deletion gated by password plus a typed confirmation. Deletion is safe because every user-owned table references user.id with onDelete: "cascade" — credits, transactions, and documents go with the account. The forms live in src/modules/auth/components/settings-forms.tsx; the server side is just user.changeEmail and user.deleteUser enabled in auth.ts.
Auth emails
Magic-link and verification emails go through src/modules/email (Resend). When RESEND_API_KEYis unset they're logged to the server console instead, so the flows stay testable locally without an account.