Next.js Auth System Using JWT, Roles, and Permissions: A Simple Guide
Ever built a Next.js app, only to realize you have no idea how to handle authentication securely? 😅 You're not alone!
I remember my first time trying to implement user roles—"Should I store permissions in localStorage? How do I protect admin routes?"—it was a mess. I ended up with spaghetti code, security holes, and a lot of frustration.
But here’s the good news: JWT (JSON Web Tokens) + role-based access control doesn’t have to be complicated. In this guide, I’ll walk you through a simple, secure, and scalable authentication system for Next.js using JWT, roles, and permissions—without overcomplicating things.
Let’s dive in!
What We’ll Cover
✅ JWT Authentication – Secure user login & token management
✅ Role-Based Access Control (RBAC) – Define user roles (e.g., Admin, User, Guest)
✅ Permission Checks – Restrict access to pages & API routes
✅ Protecting Routes – Server-side & client-side guards
By the end, you’ll have a production-ready auth system. 🚀
Step 1: Setting Up Next.js & JWT Auth
1. Install Dependencies
We’ll use:
jsonwebtoken(JWT generation & verification)bcryptjs(password hashing)next-iron-session(secure session management)
npm install jsonwebtoken bcryptjs next-iron-session
2. Create a Basic Login API
// pages/api/login.js
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
export default async function handler(req, res) {
if (req.method !== 'POST') return res.status(405).end();
const { email, password } = req.body;
// 1. Find user in DB (mock example)
const user = { id: 1, email: 'user@example.com', password: '$2a$10$hashedPassword', role: 'user' };
// 2. Verify password
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) return res.status(401).json({ error: 'Invalid credentials' });
// 3. Generate JWT
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.status(200).json({ token });
}
Step 2: Storing & Validating JWT Securely
Option 1: HTTP-Only Cookies (Recommended for Security)
// pages/api/login.js (updated)
res.setHeader('Set-Cookie', `token=${token}; HttpOnly; Secure; Path=/; SameSite=Strict`);
res.status(200).json({ success: true });
Option 2: localStorage (for Frontend Access)
// After successful login (client-side)
localStorage.setItem('token', token);
Step 3: Role-Based Access Control (RBAC)
1. Define User Roles
const ROLES = {
ADMIN: 'admin',
USER: 'user',
GUEST: 'guest',
};
2. Protect API Routes (Middleware)
// pages/api/admin-route.js
import jwt from 'jsonwebtoken';
export default function handler(req, res) {
const token = req.cookies.token || req.headers.authorization?.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Check if user is admin
if (decoded.role !== ROLES.ADMIN) {
return res.status(403).json({ error: 'Forbidden' });
}
// Proceed if authorized
res.status(200).json({ secretAdminData: "You're in!" });
} catch (err) {
res.status(401).json({ error: 'Unauthorized' });
}
}
Step 4: Protecting Frontend Routes
1. Client-Side Guard (Next.js Middleware or useEffect Check)
// hooks/useAuth.js
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export function useAuth(requiredRole) {
const router = useRouter();
useEffect(() => {
const token = localStorage.getItem('token');
if (!token) router.push('/login');
const decoded = jwt.decode(token);
if (decoded.role !== requiredRole) router.push('/unauthorized');
}, []);
}
2. Usage in a Protected Page
// pages/admin/dashboard.js
import { useAuth } from '../../hooks/useAuth';
export default function AdminDashboard() {
useAuth('admin'); // Redirects if not admin
return <div>Welcome, Admin!</div>;
}
Step 5: Handling Permissions (Optional)
For fine-grained control:
// utils/permissions.js
const PERMISSIONS = {
DELETE_POST: 'delete_post',
EDIT_USER: 'edit_user',
};
export function hasPermission(user, permission) {
return user.permissions?.includes(permission);
}
Final Thoughts & Best Practices
✔ Never store JWT in localStorage if you can avoid it (use HTTP-only cookies).
✔ Always validate tokens server-side (don’t trust client-side checks).
✔ Use short-lived tokens (e.g., 1h expiry) + refresh tokens for better security.
FAQs (Common Struggles)
Q: Should I use Next.js API routes or an external auth service like Auth0?
A: For small apps, Next.js API is fine. For production, consider Auth0/Firebase for scalability.
Q: How do I handle token expiration?
A: Implement a refresh token system or redirect to login when expired.
Q: Is JWT secure enough?
A: Yes, if used correctly (secure storage, short expiry, proper validation).
Your Turn!
What’s your biggest struggle with Next.js auth? JWT confusion? Role management? Let me know in the comments! 👇
Happy coding! 🚀🔥
Projects to explore next
Source-code projects from the KritiMyantra catalogue. Check each project's details before buying.
Flask Authentication System (Free Version) - Kritim Yantra
🛠Features✔ Google & GitHub Authentication✔ User Registration & Login✔ Profile/Dashboard Access✔ Secure & Mi...
Python Flask Blog Project with Admin Panel
Are you ready to launch a powerful, modern, and fully customizable blog? Look no further! Introducing the Flask-Blog Pro...
Laravel 12 + ReactJS Starter Kit: Add Multi-Language Support (Arabic, English, Spanish) with Blog CRUD
🚀 Installation & Setup Download & Extract Get the ZIP from kritimyantra.com and extract it. Includes preconfigu...
Comments
No comments yet. Be the first to comment.
Sign in to join the discussion.