Docs / Authentication
Authentication
Auth is handled by better-auth with the passkey plugin enabled. Email/password is the fallback; passkey is preferred when the browser supports it.
Setup #
The auth instance lives in lib/auth.ts. It's imported by both server and client; the server-only and client-only helpers live in lib/auth-server.ts and lib/auth-client.ts respectively.
You need two env vars:
BETTER_AUTH_SECRET=<32 bytes of random — e.g. `openssl rand -hex 32`>
BETTER_AUTH_URL=http://localhost:3000 # in devIn production, set BETTER_AUTH_URL to your canonical site URL.
The sign-in flow #
The starter ships with a working /sign-in page. It detects whether the browser supports WebAuthn and presents passkey as the primary option; email/password is shown as a secondary path.
- New user: enters email → starter sends a verification link → user clicks → registers a passkey.
- Returning user: clicks "Sign in with passkey" → the browser handles the WebAuthn ceremony → session established.
- Email/password fallback: standard form, no surprises.
Sessions are stored in the database (the session table). The session cookie is HTTP-only and Secure in production.
Protecting a route #
Every page or layout under app/studio/ already has session protection through app/studio/layout.tsx. To gate a new route elsewhere, call the server-side helper:
import { redirect } from "next/navigation";
import { getSession } from "@/lib/auth-server";
export default async function GatedPage() {
const session = await getSession();
if (!session) redirect("/sign-in");
// ...
}Reading the session in a server component #
import { getSession } from "@/lib/auth-server";
const session = await getSession();
// session?.user contains { id, email, name, ... }Adding a sign-out button #
Use the client helper:
"use client";
import { signOut } from "@/lib/auth-client";
<button onClick={() => signOut()}>Sign out</button>signOut() clears the session and refreshes the route.
Email verification and password reset #
Both flows use Resend (optional dependency). If you don't set RESEND_API_KEY, the starter falls back to logging the email content to the console — useful in development.
Roles and authorization #
The starter ships with authentication only. There are no built-in roles or permission models. Add them when you have a concrete authorization story; don't bolt on an RBAC system before you need one.