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. Instead of bolting it on with workarounds, you can handle authentication where it belongs: on the server, integrated with React Server Components and modern layouts.
This chapter shows you how — and why — NextAuth, middleware, and cookies-based logic are now your best tools for securing full-stack apps.

Why Auth Is Different in the App Router
The app/
directory changes how and where you authenticate:
- No
getServerSideProps
(RSC replaces it) - More focus on middleware, server sessions, and
cookies()
Auth Options in Next.js 15+
Method | Ideal Use Case | Tools |
---|---|---|
NextAuth.js | OAuth, email login, JWT sessions | NextAuth |
Custom Sessions | Token-based auth, RBAC | cookies() , headers() |
Middleware Auth | Global route protection | middleware.ts |
NextAuth.js in the App Router
Install:
npm install next-auth
Create:
app/ api/ auth/ [...nextauth]/route.ts
route.ts
import NextAuth from 'next-auth'; import GitHubProvider from 'next-auth/providers/github'; export const authOptions = { providers: [GitHubProvider({ clientId: '', clientSecret: '' })], }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
Protecting Routes
You’ll create a server-side check:
// app/dashboard/page.tsx import { getServerSession } from 'next-auth'; import { authOptions } from '@/app/api/auth/[...nextauth]/route'; export default async function Dashboard() { const session = await getServerSession(authOptions); if (!session) return <RedirectToLogin />; return <h1>Welcome, {session.user.name}</h1>; }
No getServerSideProps
needed. Just await
inside the component — RSC-style.
Middleware for Global Route Protection
Create:
// middleware.ts import { NextResponse } from 'next/server'; import { getToken } from 'next-auth/jwt'; export async function middleware(req) { const token = await getToken({ req, secret: process.env.SECRET }); const isAuthPage = req.nextUrl.pathname.startsWith('/login'); if (!token && !isAuthPage) { return NextResponse.redirect(new URL('/login', req.url)); } return NextResponse.next(); }
Configure:
// next.config.js module.exports = { matcher: ['/dashboard/:path*', '/admin/:path*'], };
Cookies-Based Auth (No NextAuth)
For pure cookie-based logic:
import { cookies } from 'next/headers'; export default function DashboardPage() { const token = cookies().get('token')?.value; if (!token) return redirect('/login'); return <h1>Secret dashboard</h1>; }
Protecting Client Components
'use client'; import { useSession } from 'next-auth/react'; export default function ClientOnly() { const { data: session } = useSession(); return <div>Welcome {session?.user?.name}</div>; }
Final Thought
Authentication in the App Router isn’t just an upgrade — it’s a simplification. You get server-driven auth, no more prop drilling, and seamless integration with React Server Components and layouts.
If you’ve been following along, here’s what we’ve covered so far:
- Chapter 1 — 3 Basic Questions About Next.js:
pages/
vsapp/
, RSCs, and navigation - Chapter 2 — Dynamic Routes in Next.js: Slugs, catch-alls, and nested routing
- Chapter 3 — React Server Components (RSC): How to render more on the server and ship less JS
- Chapter 4 — SSR vs SSG vs ISR: Choosing the right rendering method
- Chapter 5 —
getServerSideProps
+ External APIs: When and how to fetch server-side data - Chapter 6 — Streaming + Suspense: Serve UI faster using progressive rendering
- Chapter 7 — You’re here!
More chapters are coming!