Think of the last app you built with authentication. How many hoops did you jump through — passing props, syncing state, juggling client and server logic? With the Next.js App Router, auth finally clicks into place. Server components, middleware, and cookies-based logic let you handle authentication where it belongs: on the server, before the page ever renders.
This chapter covers the 2026 approach — using Auth.js (NextAuth v5), which introduced a completely new API. If your code still references getServerSession or authOptions, this guide will bring you up to date.

Why Auth Works Differently in the App Router
The app/ directory removes the patterns that auth used to rely on:
- No
getServerSideProps— React Server Components replace it - No need to prop-drill session state — server components fetch it directly
- Middleware (or Proxy in Next.js 16) runs at the edge, before any page renders, making it the right place for global route protection
The result is cleaner, server-driven auth with less boilerplate.
Auth Options in Next.js 15 and 16
| Method | Best For | Tools |
|---|---|---|
| Auth.js (NextAuth v5) | OAuth, email, credentials | auth(), handlers |
| Custom cookie sessions | Token-based auth, RBAC | cookies() from next/headers |
| Middleware / Proxy only | Global redirects, no library | middleware.ts or proxy.ts + cookies() |
Auth.js (NextAuth v5) — The New API
NextAuth v5 ships as Auth.js and rewrites the configuration model. As of mid-2026, v5 is still published on the beta tag — next-auth@latest still resolves to v4. Install v5 explicitly:
npm install next-auth@beta
Step 1: Create auth.ts
Create a single auth.ts file at your project root. This replaces the old authOptions export:
// auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [GitHub],
callbacks: {
// Required if you re-export auth as middleware/proxy for auto-redirects
authorized: async ({ auth }) => {
return !!auth
},
},
})
Step 2: Add the Route Handler
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth"
export const { GET, POST } = handlers
That’s it. No authOptions, no wrapping — re-export the handlers from your auth.ts file.
Step 3: Protect Server Components with auth()
The old getServerSession(authOptions) is gone. In v5, you call auth() directly:
// 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>Welcome, {session.user?.name}</h1>
}
One import, one call, no extra arguments.
Middleware (and Proxy) for Global Route Protection
For routes you want to protect without touching each page individually, wire Auth.js into the request pipeline.
Next.js 15 and earlier — use middleware.ts:
// middleware.ts
export { auth as middleware } from "@/auth"
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
}
Next.js 16+ — the file convention was renamed to proxy.ts:
// proxy.ts
export { auth as proxy } from "@/auth"
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
}
The matcher config tells Next.js which routes to run this on. With the authorized callback in auth.ts, unauthenticated users hitting any matched path are redirected to your sign-in page.
For custom redirect logic — for example, redirecting to different pages based on user role — wrap it instead:
// middleware.ts (Next.js 15) or proxy.ts default export (Next.js 16)
import { auth } from "@/auth"
import { NextResponse } from "next/server"
export default auth((req) => {
const isLoggedIn = !!req.auth
const isAdminRoute = req.nextUrl.pathname.startsWith("/admin")
if (isAdminRoute && req.auth?.user?.role !== "admin") {
return NextResponse.redirect(new URL("/unauthorized", req.url))
}
if (!isLoggedIn) {
return NextResponse.redirect(new URL("/login", req.url))
}
})
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
}
Cookies-Based Auth Without a Library
If you don’t need OAuth and want full control, you can handle auth purely with cookies:
// app/dashboard/page.tsx
import { cookies } from "next/headers"
import { redirect } from "next/navigation"
export default async function DashboardPage() {
const cookieStore = await cookies() // cookies() is async in Next.js 15+
const token = cookieStore.get("token")?.value
if (!token) redirect("/login")
return <h1>Secret dashboard</h1>
}
Note that cookies() is async in Next.js 15 and later — always await it.
Protecting Client Components
When you need session data in a client component, wrap your app with SessionProvider and use the useSession hook:
// app/layout.tsx
import { SessionProvider } from "next-auth/react"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<SessionProvider>{children}</SessionProvider>
</body>
</html>
)
}
// components/UserMenu.tsx
"use client"
import { useSession } from "next-auth/react"
export default function UserMenu() {
const { data: session } = useSession()
if (!session) return <a href="/login">Sign in</a>
return <span>Welcome, {session.user?.name}</span>
}
Protecting Routes at the Layout Level
One underused pattern in the App Router: protecting an entire section by adding an auth check to its layout. Every page nested under that layout inherits the protection without repeating the check:
// app/dashboard/layout.tsx
import { auth } from "@/auth"
import { redirect } from "next/navigation"
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
const session = await auth()
if (!session) redirect("/login")
return <>{children}</>
}
Any page under app/dashboard/ is automatically protected.
What’s Next
Authentication in the App Router isn’t just an upgrade — it’s a simplification. Server-driven sessions, one auth() call, and middleware (or proxy) that runs before the page renders. No prop drilling, no sync issues.
If you’ve been following this series, here’s where we are:
- Chapter 1 — pages/ vs app/, RSCs, and navigation
- Chapter 2 — Dynamic routes, catch-alls, generateStaticParams
- Chapter 3 — React Server Components
- Chapter 4 — SSR vs SSG vs ISR
- Chapter 5 — getServerSideProps + External APIs
- Chapter 6 — Streaming + Suspense
- Chapter 7 — You’re here
More chapters coming. Keep shipping.