Skip to main content

Role Based Access Control Implemenation In Next Js

ยท 5 min read
Sivabharathy

Learn how to implement Role-Based Access Control (RBAC) in Next.js with JWT authentication, refresh tokens, secure API routes, and role-based UI rendering. This step-by-step guide covers authentication, protected routes, middleware security, and dynamic UI updates for a secure Next.js application

๐Ÿ“Œ Introductionโ€‹

Managing access control in a Next.js application is crucial, especially for admin dashboards, multi-user systems, and API security. In this guide, we'll implement RBAC (Role-Based Access Control) with:
โœ… JWT Authentication (Access & Refresh Tokens)
โœ… Local Storage & Cookies for Session Management
โœ… Role-Based UI Rendering
โœ… Client-Side Protection (HOC: AuthGuard)
โœ… Server-Side Middleware for Secure Routing
โœ… Secure API Protection

By the end, your app will have secure authentication, refresh token handling, and role-based page access.


๐Ÿ›  1๏ธโƒฃ Authentication System (Login & Refresh Tokens)

๐Ÿ”น User Authentication with JWT Tokensโ€‹

We'll generate two JWTs:

  • Access Token (expires in 15 min)
  • Refresh Token (used to get a new Access Token when expired)

๐Ÿ”น Mock User Dataโ€‹

We simulate a user database:

const users = [
{ id: 1, username: "admin", password: "admin123", role: "admin" },
{ id: 2, username: "user", password: "user123", role: "user" },
];

๐Ÿ”น 2๏ธโƒฃ Create API Routes for Login & Token Refreshโ€‹

๐Ÿ“Œ pages/api/login.js (Generate Access & Refresh Tokens)โ€‹

import jwt from "jsonwebtoken";
import { serialize } from "cookie";

const SECRET_KEY = "SECRET_KEY";
const REFRESH_SECRET = "REFRESH_SECRET";

export default function handler(req, res) {
if (req.method !== "POST") return res.status(405).end();

const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);

if (!user) return res.status(401).json({ message: "Invalid credentials" });

// Generate Tokens
const accessToken = jwt.sign({ id: user.id, role: user.role }, SECRET_KEY, { expiresIn: "15m" });
const refreshToken = jwt.sign({ id: user.id, role: user.role }, REFRESH_SECRET, { expiresIn: "7d" });

res.setHeader("Set-Cookie", serialize("refreshToken", refreshToken, { httpOnly: true, path: "/" }));
res.status(200).json({ accessToken, role: user.role });
}

โœ… Stores refresh token in cookies
โœ… Returns access token & user role


๐Ÿ“Œ pages/api/refresh.js (Generate New Access Token)โ€‹

import jwt from "jsonwebtoken";

const SECRET_KEY = "SECRET_KEY";
const REFRESH_SECRET = "REFRESH_SECRET";

export default function handler(req, res) {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) return res.status(403).json({ message: "No refresh token" });

try {
const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
const newAccessToken = jwt.sign({ id: decoded.id, role: decoded.role }, SECRET_KEY, { expiresIn: "15m" });

res.status(200).json({ accessToken: newAccessToken });
} catch {
res.status(403).json({ message: "Invalid refresh token" });
}
}

โœ… Verifies refresh token & issues new access token


๐Ÿ” 3๏ธโƒฃ Client-Side Authentication

๐Ÿ”น Create a Login Pageโ€‹

pages/login.js

import { useState } from "react";
import { useRouter } from "next/router";

export default function Login() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();

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

if (res.ok) {
const data = await res.json();
localStorage.setItem("accessToken", data.accessToken);
localStorage.setItem("role", data.role);
router.push("/dashboard");
} else {
alert("Invalid credentials");
}
};

return (
<form onSubmit={handleLogin}>
<input type="text" placeholder="Username" onChange={(e) => setUsername(e.target.value)} required />
<input type="password" placeholder="Password" onChange={(e) => setPassword(e.target.value)} required />
<button type="submit">Login</button>
</form>
);
}

๐Ÿ”น Stores access token & role in localStorage
๐Ÿ”น Redirects users after login


๐Ÿ›ก 4๏ธโƒฃ Implementing Role-Based Page Protection (HOC)โ€‹

๐Ÿ”น AuthGuard.js (Restrict Pages Based on Role)โ€‹

import { useRouter } from "next/router";
import { useEffect, useState } from "react";

const AuthGuard = ({ children, allowedRoles }) => {
const [authenticated, setAuthenticated] = useState(false);
const router = useRouter();

useEffect(() => {
const token = localStorage.getItem("accessToken");
const role = localStorage.getItem("role");

if (!token || !allowedRoles.includes(role)) {
router.push("/login");
} else {
setAuthenticated(true);
}
}, []);

if (!authenticated) return null;

return children;
};

export default AuthGuard;

โœ… Restricts pages based on roles


๐ŸŽฏ 5๏ธโƒฃ Role-Based UI Renderingโ€‹

๐Ÿ“Œ components/Navbar.jsโ€‹

import { useEffect, useState } from "react";

export default function Navbar() {
const [role, setRole] = useState("");

useEffect(() => {
setRole(localStorage.getItem("role"));
}, []);

return (
<nav>
<a href="/">Home</a>
{role === "admin" && <a href="/admin-dashboard">Admin Panel</a>}
{role && <a href="/user-dashboard">User Dashboard</a>}
{!role && <a href="/login">Login</a>}
</nav>
);
}

โœ… Dynamically updates UI based on role


โš™ 6๏ธโƒฃ Secure Route Protection Using Middlewareโ€‹

๐Ÿ“Œ middleware.jsโ€‹

import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";

const SECRET_KEY = "SECRET_KEY";

export function middleware(req) {
const token = req.cookies.get("token");

try {
if (token) {
const decoded = jwt.verify(token, SECRET_KEY);

if (req.nextUrl.pathname.startsWith("/admin-dashboard") && decoded.role !== "admin") {
return NextResponse.redirect(new URL("/login", req.url));
}
} else {
return NextResponse.redirect(new URL("/login", req.url));
}
} catch {
return NextResponse.redirect(new URL("/login", req.url));
}

return NextResponse.next();
}

โœ… Prevents direct access even if localStorage is modified


โœ… Conclusion

๐ŸŽฏ Built a Secure Authentication System
๐Ÿ”น Implemented JWT with Refresh Tokens
๐Ÿ”น Added Role-Based Page & UI Protection
๐Ÿ”น Secured API & Middleware for route protection

Now your Next.js app is fully secured with RBAC & Refresh Tokens! ๐Ÿš€