Role-Based Access Control (RBAC)
Relevant source files
The following files were used as context for generating this wiki page:
The Role-Based Access Control (RBAC) system in this project is designed to restrict access to specific parts of the application based on user roles. It operates on both the backend and frontend to provide a comprehensive security layer. The backend enforces access rules at the API level, while the frontend dynamically renders routes and components based on the authenticated user's permissions.
The system defines two primary roles: user and admin. The admin role has elevated privileges, allowing access to administrative dashboards and management functionalities that are hidden from regular user roles. This ensures that sensitive operations and data are protected.
Backend Implementation
The backend is the primary authority for enforcing access control. It uses a combination of a user data model and middleware to protect API endpoints.
User Data Model
The user's role is defined in the User schema. This is the source of truth for a user's permissions.
Sources: backend/src/models/User.js
Key Schema Fields
| Field | Type | Description |
|---|---|---|
role | String | Defines the user's role. It is an enum that can be either "user" or "admin". The default value is "user". |
password | String | The user's hashed password. It is configured with select: false to prevent it from being returned in queries by default. |
Sources: backend/src/models/User.js:41-45, backend/src/models/User.js:33-38
User Schema ER Diagram
The following diagram illustrates the structure of the User model with a focus on the role attribute.
Sources: backend/src/models/User.js:10-58
Authorization Middleware
The backend uses two key middleware functions, protect and authorize, to secure its API routes. These are defined in backend/src/middleware/auth.js.
protect Middleware
This middleware is responsible for authentication. It validates the JSON Web Token (JWT) sent with the request and attaches the user's information, including their role, to the req object for subsequent middleware to use.
Logic Flow:
- Extracts the JWT from the request cookie (
token) or theAuthorizationheader. - If no token is found, it returns a
401 Unauthorizederror. - Verifies the token using the
JWT_SECRET. - If the token is valid, it decodes the payload and attaches a
userobject (containingidandrole) to thereqobject. - If the token is invalid or expired, it returns a
401 Unauthorizederror.
Sources: backend/src/middleware/auth.js:8-28
The sequence diagram below shows the protect middleware process.
Sources: backend/src/middleware/auth.js:8-28
authorize Middleware
This is a higher-order function that provides role-based authorization. It takes a list of allowed roles and checks if the authenticated user's role is one of them.
Logic Flow:
- The middleware is initialized with one or more role names (e.g.,
authorize('admin')). - It checks
req.user.role, which was set by theprotectmiddleware. - If the user's role is included in the allowed roles, it calls
next()to proceed. - If the role is not permitted, it returns a
403 Forbiddenerror.
Sources: backend/src/middleware/auth.js:31-41
The following flowchart illustrates the authorize middleware's decision process.
Sources: backend/src/middleware/auth.js:31-41
Middleware Usage Example
The users route file demonstrates how these middlewares are used together. All routes are first protected by protect, and specific routes that require admin privileges, like GET /api/users and DELETE /api/users/:id, are further protected by authorize('admin').
// backend/src/routes/users.js
// All routes below require authentication
router.use(protect);
// This route requires 'admin' role
router.get("/", authorize("admin"), getUsers);
// This route is accessible to authenticated users (own profile) or admins
router.get("/:id", userIdValidation, getUser);
// This route requires 'admin' role
router.delete("/:id", userIdValidation, authorize("admin"), deleteUser);
Sources: backend/src/routes/users.js:40-55
Frontend Implementation
The frontend uses React Router and custom components to control access to pages and UI elements based on the user's role, which is typically retrieved from an AuthContext.
Protected Route Configuration
The main application routing in frontend/src/App.js defines which routes are public, which are protected for any authenticated user, and which are restricted to admins.
The <RequireAdmin /> component acts as a wrapper for all routes that should only be accessible to administrators.
// frontend/src/App.js
// ...
<Route element={<RequireAdmin />}>
<Route path="/admin" element={<LayoutAdmin />}>
<Route path="overview" element={<AdminOverview/>}/>
<Route path="users" element={<UserManagerment />} />
<Route path="devices" element={<DeviceManagerment />} />
{/* ... other admin routes */}
</Route>
</Route>
// ...
Sources: frontend/src/App.js:49-58
The diagram below shows the routing hierarchy.
Sources: frontend/src/App.js:31-59
Admin Route Guard (RequireAdmin.jsx)
This component is a route guard specifically for admin pages. It checks if the authenticated user has the admin role. If not, it redirects the user, typically to the sign-in page or a "not found" page. It wraps the <Outlet /> component from react-router-dom to render the nested child routes if the user is authorized.
Note: The exact implementation of RequireAdmin.jsx is not fully shown in the provided files, but its usage in App.js implies it checks for an admin role and renders <Outlet /> on success or a <Navigate /> component on failure.
Sources: frontend/src/App.js:49, frontend/src/components/common/RequireAdmin.jsx
Component-Level Guarding (RoleGuard.jsx)
For more granular control within a page, the RoleGuard component can be used to conditionally render UI elements. It takes an array of roles as a prop and renders its children only if the current user's role is included in the array.
This is useful for showing or hiding buttons, menu items, or data fields that are specific to certain roles without creating separate pages.
// frontend/src/components/common/RoleGuard.jsx
import { useAuth } from "../../context/AuthContext";
export default function RoleGuard({ roles = [], children }) {
const { user } = useAuth();
const ok = user?.role && roles.includes(user.role);
return ok ? children : null;
}
Sources: frontend/src/components/common/RoleGuard.jsx:1-6
Summary
The RBAC system effectively secures the application by combining backend enforcement with frontend adaptability. The backend acts as the definitive gatekeeper for all data and actions via middleware, while the frontend provides a tailored user experience by hiding or showing routes and components based on the user's role. This dual-layer approach ensures both security and a clean user interface.