Authentication looks simple on the surface — until we build a real Next.js application with SSR, middleware, and protected routes.
Most developers start with JWT, then run into issues such as:
- Tokens not being available on first page load
- Authentication breaking during SSR
- Security concerns with localStorage
- Confusion around cookies vs tokens So let’s clear everything properly, from the ground up, specifically in the context of Next.js.
What Is JWT?
JWT (JSON Web Token) is a compact, self-contained token used to transfer claims between parties.
A JWT consists of three parts:
"header.payload.signature"
What it contains
- Header → token type and signing algorithm
- Payload → user data such as userId, roles, expiry
- Signature → ensures integrity and authenticity
Key properties of JWT
- Stateless (the server does not store sessions)
- Usually sent via an Authorization header
Commonly stored in:
- localStorage (unsafe)
- memory (temporary)
- cookies (preferred)
JWT itself is not the problem.
The real issue is how and where we store it.
What Are Cookies?
Cookies are browser-managed key–value storage that automatically travel with HTTP requests.
Example format:
"Set-Cookie: auth_token=xyz; HttpOnly; Secure; SameSite=Strict"
Key properties of cookies
- Automatically sent with every request
- Available during server-side rendering
Can be configured as:
- HttpOnly (JavaScript cannot access it)
- Secure (HTTPS only)
- SameSite (CSRF protection)
Cookies are not an authentication mechanism.
They are a transport and storage mechanism.
JWT vs Cookies — The Most Important Clarification
JWT and cookies are not competitors.This misunderstanding causes most authentication bugs.JWT defines what proves identityCookies define how that proof is stored and sent.
We can:
- Store JWT inside cookies (best approach)
- Send JWT via headers (problematic in Next.js)
- Store sessions inside cookies
So the real comparison is not JWT vs cookies, but:
JWT in headers vs JWT in cookies
Why JWT Alone Fails in Next.js
Next.js is not just a frontend framework.
It executes code in multiple environments:
- Server Components
- Route Handlers
- Middleware
- SSR / SSG
- Client Components
The Core Problem
JWT stored in:
- localStorage
- sessionStorage
- JavaScript memory
is not accessible on the server.This means:
- The first request has no auth context
- SSR cannot fetch protected data
- Middleware cannot validate users
- Auth-based redirects fail
- The SSR Problem (The Real Reason Cookies Win)
Consider a user visiting a protected route like /dashboard. With JWT in localStorage the server renders the page. The server has no access to the JWT. The page renders as unauthenticated and client hydrates. JWT is read from storage so the data is refetched.
This causes:
- UI flickering
- Security inconsistencies
- Poor user experience
With Cookies
- The request reaches the server
- Cookies are automatically attached
- The server validates authentication
- Protected data loads on the first render
- This single point explains why cookies dominate in Next.js authentication.
- Extra Capabilities Cookies Provide (Beyond JWT)
- Cookies unlock platform-level capabilities that JWT alone cannot.
Middleware Authentication
Because cookies are available at the edge, middleware can read them and make routing decisions before a page renders.
Example logic (not code):
"We read the auth cookie in middleware and redirect unauthenticated users before rendering protected routes."
This is impossible with localStorage.
Security by Design
Cookies provide:
- HttpOnly protection against XSS
- SameSite protection against CSRF
- Secure flag for HTTPS-only transmission
- JWT stored in localStorage provides none of these protections.
- Works Everywhere in Next.js
Cookies are accessible in:
- Server Components
- Route Handlers
- Middleware
- API Routes
- SSR
This creates one unified authentication model across the entire application.
Why Next.js Apps Prefer Cookies (Even When Using JWT)
Most production-grade Next.js apps follow this flow:
"User logs in → Server generates JWT → JWT stored in HttpOnly cookie → Cookie sent automatically → Server validates JWT"
So JWT is still used, but cookies act as the delivery and storage layer.
This approach gives us:
- Stateless authentication
- SSR compatibility
- Strong security guarantees
- Cleaner architecture
When JWT in Headers Still Makes Sense
JWT sent via headers can still be useful for:
- Pure backend APIs
- Mobile applications
- Third-party integrations
- Systems without SSR
But for Next.js full-stack applications, cookies are simply the better choice.
Cookies solve:
- First-request authentication
- Server-side authorization
- Middleware protection
- Major security vulnerabilities
JWT does not disappear —
it just stops being misused.
Top comments (0)