Creating a Custom Authentication System in Next.js With JWT

Creating a Custom Authentication System in Next.js With JWT While frameworks like NextAuth.js provide plug-and-play authentication, building a custom JWT-based system in Next.js gives you full control over user sessions and security. In this guide, we’ll create a basic authentication flow using JSON Web Tokens (JWT), covering user login, protected routes, and token verification. Step 1: Setup Your Project Start with a Next.js project: npx create-next-app jwt-auth-app cd jwt-auth-app npm install jsonwebtoken bcryptjs Step 2: Mock Users and Login API Create a simple login API route at /pages/api/login.js: import jwt from "jsonwebtoken"; import bcrypt from "bcryptjs"; const users = [ { id: 1, username: "admin", password: bcrypt.hashSync("password", 10) }, ]; export default function handler(req, res) { const { username, password } = req.body; const user = users.find((u) => u.username === username); if (user && bcrypt.compareSync(password, user.password)) { const token = jwt.sign({ id: user.id, username: user.username }, "secret", { expiresIn: "1h", }); res.status(200).json({ token }); } else { res.status(401).json({ message: "Invalid credentials" }); } } Step 3: Protect API Routes With Middleware Create a helper function to verify JWTs: // lib/auth.js import jwt from "jsonwebtoken"; export function verifyToken(req) { const authHeader = req.headers.authorization; if (!authHeader) return null; const token = authHeader.split(" ")[1]; try { return jwt.verify(token, "secret"); } catch { return null; } } Now use it in a protected route: // pages/api/protected.js import { verifyToken } from "../../lib/auth"; export default function handler(req, res) { const user = verifyToken(req); if (!user) { return res.status(401).json({ message: "Unauthorized" }); } res.status(200).json({ message: "Protected data", user }); } Step 4: Frontend Integration In your component or login form, send credentials and store the JWT: const handleLogin = async () => { const res = await fetch("/api/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "admin", password: "password" }), }); const data = await res.json(); if (res.ok) { localStorage.setItem("token", data.token); } }; Step 5: Call Protected Routes const fetchProtected = async () => { const token = localStorage.getItem("token"); const res = await fetch("/api/protected", { headers: { Authorization: `Bearer ${token}`, }, }); const data = await res.json(); console.log(data); }; Security Tips Store tokens securely (e.g., HTTP-only cookies for production). Use environment variables for your JWT secret. Implement refresh tokens for longer sessions. Conclusion By building your own authentication system in Next.js using JWTs, you gain control over session behavior and token structure. This approach is flexible and scalable for custom app needs. While this example is basic, it sets the foundation for a more secure and robust solution. If this post helped you, consider supporting me: buymeacoffee.com/hexshift

Apr 12, 2025 - 17:40
 0
Creating a Custom Authentication System in Next.js With JWT

Creating a Custom Authentication System in Next.js With JWT

While frameworks like NextAuth.js provide plug-and-play authentication, building a custom JWT-based system in Next.js gives you full control over user sessions and security. In this guide, we’ll create a basic authentication flow using JSON Web Tokens (JWT), covering user login, protected routes, and token verification.

Step 1: Setup Your Project

Start with a Next.js project:

npx create-next-app jwt-auth-app
cd jwt-auth-app
npm install jsonwebtoken bcryptjs

Step 2: Mock Users and Login API

Create a simple login API route at /pages/api/login.js:

import jwt from "jsonwebtoken";
import bcrypt from "bcryptjs";

const users = [
  { id: 1, username: "admin", password: bcrypt.hashSync("password", 10) },
];

export default function handler(req, res) {
  const { username, password } = req.body;
  const user = users.find((u) => u.username === username);
  
  if (user && bcrypt.compareSync(password, user.password)) {
    const token = jwt.sign({ id: user.id, username: user.username }, "secret", {
      expiresIn: "1h",
    });
    res.status(200).json({ token });
  } else {
    res.status(401).json({ message: "Invalid credentials" });
  }
}

Step 3: Protect API Routes With Middleware

Create a helper function to verify JWTs:

// lib/auth.js
import jwt from "jsonwebtoken";

export function verifyToken(req) {
  const authHeader = req.headers.authorization;
  if (!authHeader) return null;

  const token = authHeader.split(" ")[1];
  try {
    return jwt.verify(token, "secret");
  } catch {
    return null;
  }
}

Now use it in a protected route:

// pages/api/protected.js
import { verifyToken } from "../../lib/auth";

export default function handler(req, res) {
  const user = verifyToken(req);
  if (!user) {
    return res.status(401).json({ message: "Unauthorized" });
  }

  res.status(200).json({ message: "Protected data", user });
}

Step 4: Frontend Integration

In your component or login form, send credentials and store the JWT:

const handleLogin = async () => {
  const res = await fetch("/api/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username: "admin", password: "password" }),
  });

  const data = await res.json();
  if (res.ok) {
    localStorage.setItem("token", data.token);
  }
};

Step 5: Call Protected Routes

const fetchProtected = async () => {
  const token = localStorage.getItem("token");

  const res = await fetch("/api/protected", {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  const data = await res.json();
  console.log(data);
};

Security Tips

  • Store tokens securely (e.g., HTTP-only cookies for production).
  • Use environment variables for your JWT secret.
  • Implement refresh tokens for longer sessions.

Conclusion

By building your own authentication system in Next.js using JWTs, you gain control over session behavior and token structure. This approach is flexible and scalable for custom app needs. While this example is basic, it sets the foundation for a more secure and robust solution.

If this post helped you, consider supporting me: buymeacoffee.com/hexshift