Authentication is a fundamental requirement in modern web applications. This guide walks through building a simple and secure JWT (JSON Web Token) authentication system in Next.js using the App Router.
What is JWT?
A JSON Web Token (JWT) is a compact, URL-safe string used to securely transmit information between a client and a server.
It consists of three parts:
Header
Payload
Signature
After a successful login, the server generates a token and sends it to the client. The client includes this token in subsequent requests for authentication.
Project Setup
Create a new Next.js application and install the required package:
npx create-next-app@latest jwt-auth-app
cd jwt-auth-app
npm install jsonwebtoken
Step 1: Add Secret Key
Create a .env.local file in the root of your project:
JWT_SECRET=your_super_secret_key
Step 2: Create Login API (Generate Token)
Create the login route:
app/api/login/route.js
export async function POST(req) { const body = await req.json(); console.log("BODY:", body);
const { username, password } = body;
if (username !== "admin" || password !== "123") { console.log("Invalid credentials"); return new Response(JSON.stringify({ error: "Invalid" }), { status: 401 }); }
console.log("Login success"); return new Response(JSON.stringify({ message: "ok" }));}
or
import jwt from "jsonwebtoken";
import { NextResponse } from "next/server";
export async function POST(req) {
const { username, password } = await req.json();
// Replace with database validation
if (username !== "admin" || password !== "123") {
return NextResponse.json({ error: "Invalid credentials" }, { status: 401 });
}
const token = jwt.sign(
{ username, role: "admin" },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
const response = NextResponse.json({ message: "Login successful" });
response.cookies.set("token", token, {
httpOnly: true,
secure: true,
path: "/",
});
return response;
}
Step 3: Create Protected API
app/api/protected/route.js
import jwt from "jsonwebtoken";
import { NextResponse } from "next/server";
export async function GET(req) {
const token = req.cookies.get("token")?.value;
if (!token) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
return NextResponse.json({
message: "Protected data",
user: decoded,
});
} catch {
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
}
}
Step 4: Add Middleware Protection
Create a middleware file at the root:
middleware.js
import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";
export function middleware(req) {
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*"],
};
Step 5: Create Login Page
app/login/page.js
"use client";
import { useState } from "react";
export default function Login() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState("");
const handleLogin = async () => { setLoading(true); setError("");
const res = await fetch("/api/login", { method: "POST", body: JSON.stringify({ username, password }), headers: { "Content-Type": "application/json", }, });
if (!res.ok) { setError("Invalid credentials"); setLoading(false); return; }
setLoading(false); window.location.href = "/dashboard"; };
return ( <div style={styles.container}> <h1 style={styles.title}>Login</h1>
<input type="text" placeholder="Username" value={username} onChange={(e) => setUsername(e.target.value)} style={styles.input} />
<input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} style={styles.input} />
{error && <p style={styles.error}>{error}</p>}
<button onClick={handleLogin} disabled={loading} style={styles.button} > {loading ? "Logging in..." : "Login"} </button> </div> );}
const styles = { container: { height: "100vh", display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", gap: "12px", fontFamily: "sans-serif", }, title: { fontSize: "28px", }, input: { padding: "10px", width: "220px", borderRadius: "6px", border: "1px solid #ccc", }, button: { padding: "10px 20px", borderRadius: "6px", border: "none", backgroundColor: "#2563eb", color: "white", cursor: "pointer", }, error: { color: "red", fontSize: "14px", },};Step 6: Create Protected Page
app/dashboard/page.js
export default function Dashboard() {
return <h1>Dashboard (Protected)</h1>;
}
Authentication Flow
The user submits login credentials
The server validates the credentials
A JWT is generated and stored in an HTTP-only cookie
Middleware runs on every request to protected routes
If the token is valid, access is granted
If the token is missing or invalid, the user is redirected to the login page
Best Practices
Use HTTP-only cookies to prevent client-side access
Enable
secure: truein production (HTTPS only)Avoid storing sensitive data in the token payload
Set expiration times for tokens
Use a strong and private secret key
Conclusion
JWT authentication in Next.js provides a stateless and scalable way to secure applications. By combining API routes, cookies, and middleware, you can implement a robust authentication system with minimal complexity.
Comments
Post a Comment