Auth.js (formerly NextAuth.js) is the standard authentication library for Next.js. Version 5 integrates deeply with the App Router, supporting OAuth providers, credentials, sessions, and middleware-based route protection.
Installation
pnpm add next-auth@betaGenerate an auth secret:
npx auth secretThis adds AUTH_SECRET to your .env.local file.
Basic Setup
Create auth.ts at the project root:
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
GitHub({
clientId: process.env.AUTH_GITHUB_ID,
clientSecret: process.env.AUTH_GITHUB_SECRET,
}),
Google({
clientId: process.env.AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
}),
],
});Create the API route at app/api/auth/[...nextauth]/route.ts:
import { handlers } from "@/auth";
export const { GET, POST } = handlers;Environment Variables
Add these to .env.local:
AUTH_SECRET="your-generated-secret"
# GitHub OAuth
AUTH_GITHUB_ID="your-github-client-id"
AUTH_GITHUB_SECRET="your-github-client-secret"
# Google OAuth
AUTH_GOOGLE_ID="your-google-client-id"
AUTH_GOOGLE_SECRET="your-google-client-secret"Sign In and Sign Out
Server Component Buttons
import { signIn, signOut, auth } from "@/auth";
export default async function AuthButtons() {
const session = await auth();
if (session?.user) {
return (
<div>
<p>Signed in as {session.user.name}</p>
<form
action={async ()=> {
"use server";
await signOut();
}}
>
<button type="submit">Sign Out</button>
</form>
</div>
);
}
return (
<div>
<form
action={async ()=> {
"use server";
await signIn("github");
}}
>
<button type="submit">Sign in with GitHub</button>
</form>
<form
action={async ()=> {
"use server";
await signIn("google");
}}
>
<button type="submit">Sign in with Google</button>
</form>
</div>
);
}Client Component Buttons
"use client";
import { signIn, signOut } from "next-auth/react";
export function SignInButton() {
return <button onClick={()=> signIn("github")}>Sign in with GitHub</button>;
}
export function SignOutButton() {
return <button onClick={()=> signOut()}>Sign Out</button>;
}Reading the Session
In Server Components
import { auth } from "@/auth";
export default async function DashboardPage() {
const session = await auth();
if (!session) {
return <p>Please sign in to view the dashboard.</p>;
}
return (
<div>
<h1>Welcome, {session.user?.name}</h1>
<p>Email: {session.user?.email}</p>
<img src={session.user?.image ?? ""} alt="Avatar" width={64} height={64} />
</div>
);
}In Server Actions
"use server";
import { auth } from "@/auth";
export async function createPost(formData: FormData) {
const session = await auth();
if (!session?.user) {
throw new Error("Unauthorized");
}
// ... create post with session.user info
}In Route Handlers
import { auth } from "@/auth";
import { NextResponse } from "next/server";
export async function GET() {
const session = await auth();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
return NextResponse.json({ user: session.user });
}Protecting Routes with Middleware
The most efficient way to protect routes is with middleware. Create or update middleware.ts:
import { auth } from "@/auth";
export default auth((req) => {
const isLoggedIn = !!req.auth;
const isOnDashboard = req.nextUrl.pathname.startsWith("/dashboard");
const isOnAdmin = req.nextUrl.pathname.startsWith("/admin");
if ((isOnDashboard || isOnAdmin) && !isLoggedIn) {
return Response.redirect(new URL("/api/auth/signin", req.nextUrl));
}
});
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
};Database Sessions with Prisma
For persistent sessions stored in your database, add the Prisma adapter:
pnpm add @auth/prisma-adapterUpdate auth.ts:
import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/app/lib/db";
import GitHub from "next-auth/providers/github";
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [GitHub],
session: { strategy: "database" },
});Add the required models to schema.prisma:
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}Run npx prisma migrate dev --name add-auth to apply the schema changes.
Extending the Session
Add custom fields to the session by using callbacks:
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
callbacks: {
async session({ session, user }) {
// Add the user ID to the session
session.user.id = user.id;
return session;
},
async authorized({ auth, request }) {
// Return true if the user is authenticated
return !!auth?.user;
},
},
});Summary
- Auth.js v5 integrates natively with the Next.js App Router.
- Configure providers (GitHub, Google, etc.) in a root
auth.tsfile. - Use
auth()to read the session in Server Components, Actions, and Route Handlers. - Use middleware to protect entire route groups efficiently.
- Add the Prisma adapter for database-backed sessions.
- Extend sessions with custom data using callbacks.