Next.js
Auth Patterns in Next.js: Sessions, JWT, and Protected Routes
How to implement login, sessions or JWT, and protect routes in the App Router without overcomplicating it.
Auth in Next.js means: where you store the credential (session cookie vs JWT), how you check it on the server, and how you protect routes. Here’s a pattern that works with the App Router.
Sessions vs JWT
Session-based auth: the server stores session data (e.g. in a DB or Redis); the client holds an opaque cookie. Each request you look up the session and get the user. JWT: the client holds a signed token; the server verifies the signature and reads the payload. No server-side session store, but revocation is harder. For most apps, sessions are simpler to reason about (logout = delete session; revoke = invalidate one place). Use JWT when you need stateless auth across services or when your auth provider only issues tokens.
Storing and sending the credential
For sessions, set an HTTP-only, secure cookie with the session id (or a signed session payload). For JWT, you can put it in an HTTP-only cookie (recommended so JS can’t read it) or send it in a header from the client; cookie is simpler and avoids XSS stealing the token. In Next.js, set cookies in Server Actions or route handlers after login; read them in middleware or server components to decide redirects and to load the user.
// After login (e.g. in a Server Action)
import { cookies } from "next/headers";
export async function login(formData: FormData) {
const res = await yourAuthAPI(formData);
if (!res.ok) return { error: "Invalid credentials" };
const { sessionToken } = await res.json();
(await cookies()).set("session", sessionToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 7,
});
redirect("/dashboard");
}Protecting routes
In middleware: read the session or JWT cookie; if missing or invalid, redirect to login for protected paths. In server components and Server Actions: look up the session or verify the JWT and get the user; if missing, redirect or return unauthorized. Don’t rely only on client-side checks; always enforce on the server so API and SSR can’t be bypassed.
// middleware.ts
export function middleware(request: NextRequest) {
const session = request.cookies.get("session")?.value;
if (!session && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}Use a small helper (e.g. getSession()) that reads the cookie and returns the user (or null) so layouts and pages can call it and show the right UI or redirect.
Summary
Choose sessions for simpler revocation and JWTs when you need stateless auth. Store the credential in an HTTP-only cookie; set it at login and read it in middleware and server code. Protect routes in middleware (redirect) and in server components/actions (enforce). That gives you a clear auth pattern for the App Router.