# Next.js 15 Handle JWT from Backend
If your **Next.js 15 frontend** receives a **JWT token from the backend**, you can handle it securely using **middleware** and **cookies**. Here's how you can process and store the token when it's passed from the backend.
---
## **📌 Steps to Handle JWT from Backend in Next.js Middleware**
1. **Receive the JWT token** from the backend API response.
2. **Store the token in an HTTP-only cookie** for security.
3. **Use middleware** to validate the token and protect routes.
4. **Send the token with authenticated requests.**
---
## **1️⃣ Fetch Token from Backend and Store in Cookie**
When a user logs in, your Next.js app will send credentials to the backend and store the received **JWT token** in an **HTTP-only** cookie.
```tsx
// src/app/login/page.tsx
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
export default function LoginPage() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();
async function handleLogin(e: React.FormEvent) {
e.preventDefault();
const res = await fetch("https://your-backend.com/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
const data = await res.json();
if (res.ok && data.token) {
// Store JWT token in an HTTP-only cookie via Next.js API route
await fetch("/api/auth/store-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token: data.token }),
});
router.push("/dashboard");
} else {
alert("Login failed");
}
}
return (
<form onSubmit={handleLogin}>
<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Username" required />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" required />
<button type="submit">Login</button>
</form>
);
}
```
---
## **2️⃣ Store Token in Secure Cookie (API Route)**
Since the frontend cannot set **HTTP-only** cookies directly, we use a Next.js **API route** to store the JWT in a secure cookie.
```ts
// src/app/api/auth/store-token/route.ts
import { NextResponse } from "next/server";
import { serialize } from "cookie";
export async function POST(req: Request) {
const { token } = await req.json();
if (!token) {
return NextResponse.json({ error: "Token is required" }, { status: 400 });
}
const response = NextResponse.json({ message: "Token stored" });
response.headers.append(
"Set-Cookie",
serialize("token", token, {
httpOnly: true, // Prevents JavaScript access (XSS protection)
secure: process.env.NODE_ENV === "production", // Use HTTPS in production
sameSite: "strict", // Prevent CSRF attacks
path: "/", // Cookie available for all routes
})
);
return response;
}
```
---
## **3️⃣ Middleware to Protect Routes**
Use **Next.js Middleware** to check if the JWT exists and is valid.
```ts
// src/middleware.ts
import { NextResponse } from "next/server";
import { jwtVerify } from "jose";
const SECRET_KEY = new TextEncoder().encode("your-secret-key");
export async function middleware(req: Request) {
const token = req.headers.get("cookie")?.split("token=")[1]?.split(";")[0];
if (!token) {
return NextResponse.redirect(new URL("/login", req.url));
}
try {
await jwtVerify(token, SECRET_KEY);
return NextResponse.next();
} catch (error) {
return NextResponse.redirect(new URL("/login", req.url));
}
}
// Apply middleware only to protected routes
export const config = {
matcher: ["/dashboard/:path*"],
};
```
---
## **4️⃣ Logout and Clear Token**
To log out, we remove the token by expiring the cookie.
```ts
// src/app/api/auth/logout/route.ts
import { NextResponse } from "next/server";
import { serialize } from "cookie";
export async function GET() {
const response = NextResponse.json({ message: "Logged out" });
response.headers.append(
"Set-Cookie",
serialize("token", "", {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
path: "/",
expires: new Date(0), // Expire the cookie
})
);
return response;
}
```
### **Client-side Logout Function**
```tsx
// Logout button
async function handleLogout() {
await fetch("/api/auth/logout");
window.location.href = "/login";
}
```
---
## **5️⃣ Fetch User from Backend Using Token**
Create an API route to fetch user details from the backend using the stored token.
```ts
// src/app/api/auth/me/route.ts
import { NextResponse } from "next/server";
export async function GET(req: Request) {
const token = req.headers.get("cookie")?.split("token=")[1]?.split(";")[0];
if (!token) {
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
}
const res = await fetch("https://your-backend.com/api/me", {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) {
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
}
const user = await res.json();
return NextResponse.json(user);
}
```
---
## **6️⃣ Fetch User on Client Side**
Fetch user data on the client using the `/api/auth/me` route.
```tsx
"use client";
import { useEffect, useState } from "react";
export default function Dashboard() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch("/api/auth/me")
.then((res) => res.json())
.then((data) => setUser(data))
.catch(() => setUser(null));
}, []);
return (
<div>
<h1>Dashboard</h1>
{user ? <p>Welcome, {user.username}!</p> : <p>Loading...</p>}
</div>
);
}
```
---
## **✅ Summary**
| **Step** | **Action** |
|----------|-----------|
| 1️⃣ | **Receive JWT token from backend** after login |
| 2️⃣ | **Store token securely in an HTTP-only cookie** via an API route |
| 3️⃣ | **Use middleware** to validate the token & protect routes |
| 4️⃣ | **Logout by clearing the token cookie** |
| 5️⃣ | **Send the token with requests to the backend** |
| 6️⃣ | **Fetch user details in a secure way** |