webdev.complete
🪪 Auth: NextAuth & Better Auth
🟢The Backend
Lesson 76 of 117
30 min

Auth.js (NextAuth v5)

Setup, providers, session strategies, adapters.

Auth.js (formerly NextAuth.js) is the most-installed auth library in the Next.js world. v5 cleaned up the API, dropped support for legacy runtimes, and embraced the App Router. In ten minutes you can have email, Google, GitHub, and Discord login working with sessions, CSRF, and cookies all handled for you.

Install & secrets

bash
pnpm add next-auth@beta
npx auth secret  # writes AUTH_SECRET to .env.local

Every provider also needs its own client ID and secret in .env.local:

.env.local
AUTH_SECRET=your-generated-secret
AUTH_GITHUB_ID=Iv1.xxx
AUTH_GITHUB_SECRET=xxx
AUTH_GOOGLE_ID=xxx.apps.googleusercontent.com
AUTH_GOOGLE_SECRET=xxx

The auth.ts hub

One file exports everything: the handlers for the route, the auth() function to read the session on the server, and signIn/signOut for client/server actions.

auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/server/db";

export const { handlers, signIn, signOut, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [GitHub, Google],
  session: { strategy: "database" },
  callbacks: {
    session({ session, user }) {
      session.user.id = user.id;
      return session;
    },
  },
});

Wire up the route handler

app/api/auth/[...nextauth]/route.ts
export { GET, POST } from "@/auth/handlers";
// Or, if you exported handlers from auth.ts:
// export { GET, POST } from "@/auth";

Session strategies: JWT vs database

  • JWT (default): session lives entirely in a signed cookie. No DB lookup per request. You cannot easily revoke. Great for edge runtimes.
  • Database: session row stored via an adapter. Requires a DB call per request but is revocable and trivially extensible. Pairs naturally with Prisma/Drizzle.
Pick database if you need to log users out
Killing a user's session means deleting that row. With JWT you have to wait for the token to expire (or maintain a denylist).

Reading the session in a Server Component

app/dashboard/page.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";

export default async function Dashboard() {
  const session = await auth();
  if (!session) redirect("/login");
  return <h1>Hi, {session.user.name}</h1>;
}

Protecting routes with middleware (proxy in Next 16)

middleware.ts
export { auth as middleware } from "@/auth";

export const config = {
  matcher: ["/dashboard/:path*", "/account/:path*"],
};

Sign in & out

tsx
// Server Action
"use server";
import { signIn, signOut } from "@/auth";

export async function loginWithGitHub() {
  await signIn("github", { redirectTo: "/dashboard" });
}

export async function logout() {
  await signOut({ redirectTo: "/" });
}

Adapters

Adapters connect Auth.js to a database. The most common in 2026:

  • @auth/prisma-adapter
  • @auth/drizzle-adapter
  • @auth/supabase-adapter, plus adapters for Mongo, Firebase, Convex, Neon, Turso, etc.

Each adapter expects a few tables (User, Account, Session, VerificationToken). The Prisma adapter has the schema in its README, copy-paste it.

Auth.js project status
In 2025 the Auth.js team announced a collaboration with Better Auth. v5 is still maintained, but a chunk of new feature work is moving elsewhere. If you're starting fresh in 2026, weigh both. See the Better Auth lesson next.

Quiz

Quiz1 / 3

What does `npx auth secret` do?

Recap

  • Install next-auth@beta, run npx auth secret, add provider env vars.
  • Centralize everything in auth.ts: exports handlers, auth, signIn, signOut.
  • JWT sessions are stateless and edge-friendly. Database sessions are revocable and richer.
  • Adapters wire Auth.js to your DB. Prisma and Drizzle are the most common.
  • Protect server components with await auth() + redirect; protect groups of routes via middleware.