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! 🚀