Authentication is one of the most important parts of any modern web application. Whether you're building a SaaS platform, e-commerce store, admin dashboard, or AI-powered application, you need a secure way to identify users and protect sensitive routes.
In this guide, we'll build JWT Authentication in Next.js using the App Router, Route Handlers, Middleware, and HTTP-only cookies.
By the end of this tutorial, you'll understand:
- What JWT authentication is
- How JWT authentication works in Next.js
- How to generate and verify JWT tokens
- How to store tokens securely
- How to protect routes using middleware
- Security best practices for production applications
What Is JWT Authentication?
JWT (JSON Web Token) is a compact and secure method for transmitting user information between the client and server.
A JWT consists of three parts:
HEADER.PAYLOAD.SIGNATURE
Example:
eyJhbGciOiJIUzI1NiIs...
The token contains user information such as:
{
"userId": "123",
"email": "john@example.com",
"role": "admin"
}
The server signs the token using a secret key. Any tampering invalidates the token.
Why Use JWT Authentication?
JWT authentication offers several advantages:
- Stateless authentication
- Fast token verification
- Easy scalability
- Works well with APIs
- Ideal for microservices
- Widely supported across frameworks
JWT is commonly used in:
- Next.js applications
- React applications
- Mobile apps
- SaaS platforms
- REST APIs
- AI applications
JWT Authentication Flow
A typical JWT authentication flow looks like this:
- User enters email and password
- Server validates credentials
- Server generates JWT token
- Token is stored in an HTTP-only cookie
- User accesses protected routes
- Middleware verifies token
- Access is granted or denied
Project Setup
Create a new Next.js application:
npx create-next-app@latest jwt-auth-demo
cd jwt-auth-demo
Install dependencies:
npm install jsonwebtoken bcryptjs
Create Environment Variables
Create a .env.local file:
JWT_SECRET=super_secret_key_here
Use a long and random secret in production.
Create Login API Route
Create:
app/api/auth/login/route.ts
import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";
export async function POST(request: Request) {
const { email, password } = await request.json();
if (
email !== "admin@example.com" ||
password !== "password123"
) {
return NextResponse.json(
{ message: "Invalid credentials" },
{ status: 401 }
);
}
const token = jwt.sign(
{
email,
role: "admin"
},
process.env.JWT_SECRET!,
{
expiresIn: "1d"
}
);
const response = NextResponse.json({
success: true
});
response.cookies.set("token", token, {
httpOnly: true,
secure: true,
sameSite: "strict",
path: "/"
});
return response;
}
This route:
- Validates credentials
- Generates a JWT token
- Stores it in an HTTP-only cookie
Create JWT Verification Utility
Create:
lib/auth.ts
import jwt from "jsonwebtoken";
export function verifyToken(token: string) {
try {
return jwt.verify(
token,
process.env.JWT_SECRET!
);
} catch {
return null;
}
}
This helper will be used throughout the application.
Protect Routes Using Middleware
Create:
middleware.ts
import { NextRequest, NextResponse } from "next/server";
import jwt from "jsonwebtoken";
export function middleware(req: NextRequest) {
const token = req.cookies.get("token")?.value;
if (!token) {
return NextResponse.redirect(
new URL("/login", req.url)
);
}
try {
jwt.verify(
token,
process.env.JWT_SECRET!
);
return NextResponse.next();
} catch {
return NextResponse.redirect(
new URL("/login", req.url)
);
}
}
export const config = {
matcher: ["/dashboard/:path*"]
};
Now every route under:
/dashboard
requires authentication.
Access User Data on the Server
Inside a protected page:
import { cookies } from "next/headers";
import jwt from "jsonwebtoken";
export default async function Dashboard() {
const cookieStore = await cookies();
const token =
cookieStore.get("token")?.value;
const user = jwt.verify(
token!,
process.env.JWT_SECRET!
);
return (
<div>
Welcome Back
</div>
);
}
The server can safely verify the token before rendering.
Create Logout API Route
Create:
app/api/auth/logout/route.ts
import { NextResponse } from "next/server";
export async function POST() {
const response = NextResponse.json({
success: true
});
response.cookies.set("token", "", {
expires: new Date(0),
path: "/"
});
return response;
}
This removes the authentication cookie.
Security Best Practices
Use HTTP-Only Cookies
Never store JWT tokens in:
localStorage
or
sessionStorage
HTTP-only cookies reduce XSS attack risks.
Use Secure Cookies
Always use:
secure: true
in production environments.
Set Token Expiration
Avoid tokens that never expire.
Example:
expiresIn: "1d"
or
expiresIn: "2h"
Validate Every Protected Request
Never trust client-side authentication.
Always verify JWTs on the server.
Use Refresh Tokens
For large applications:
- Short-lived access token
- Long-lived refresh token
This improves overall security.
Common JWT Authentication Mistakes
Storing Tokens in Local Storage
This makes tokens vulnerable to XSS attacks.
Using Weak Secrets
Avoid:
JWT_SECRET=123456
Use long, randomly generated secrets.
Missing Token Expiration
Expired tokens improve security.
Relying Only on Frontend Checks
Attackers can bypass frontend checks.
Always validate tokens on the backend.
JWT vs Session Authentication
| Feature | JWT | Session |
|---|---|---|
| Stateless | Yes | No |
| Scalable | Yes | Limited |
| Server Storage | No | Yes |
| API Friendly | Excellent | Good |
| Microservices | Excellent | Moderate |
JWT is often preferred for modern cloud-native applications.
Conclusion
JWT authentication remains one of the most popular approaches for securing Next.js applications. By combining JWT tokens, HTTP-only cookies, Route Handlers, and Middleware, you can build a secure authentication system that scales with your application.
The key to secure JWT authentication is not just generating tokens but storing them safely, validating them correctly, and following security best practices.
Whether you're building a SaaS product, admin dashboard, AI application, or enterprise platform, implementing JWT authentication correctly will provide a strong foundation for user security.
FAQs
Is JWT better than sessions?
JWT is generally better for APIs and distributed systems, while sessions are often simpler for traditional web applications.
Should JWT be stored in localStorage?
No. Use HTTP-only cookies whenever possible.
Can JWT authentication be used with Next.js App Router?
Yes. Next.js App Router works very well with JWT authentication using Route Handlers and Middleware.
How long should a JWT token last?
Most applications use expiration periods between 15 minutes and 24 hours depending on security requirements.
Is JWT secure?
Yes, when used with strong secrets, secure cookies, HTTPS, expiration times, and proper server-side validation.
Top comments (0)