Role-Based Access Control (RBAC)
Authentication answers 'who are you?' — RBAC answers 'what are you allowed to do?' Role-Based Access Control assigns permissions to roles, and roles to users. A user inherits all permissions of their role(s). This is simpler to manage than per-user permission lists and scales well as your application grows from dozens to thousands of users. In Express, RBAC is typically implemented as a middleware chain that runs after JWT authentication.
Defining Roles and Permissions
A clean RBAC implementation separates three concerns: the role definitions (what roles exist), the permission map (what each role can do), and the enforcement middleware (which routes require which permissions). This separation lets you update permissions without touching route code and makes permission audits straightforward.
// config/roles.js
const ROLES = {
USER: 'user',
EDITOR: 'editor',
ADMIN: 'admin'
};
const PERMISSIONS = {
[ROLES.USER]: [
'read:products',
'read:own-orders',
'write:own-profile'
],
[ROLES.EDITOR]: [
'read:products',
'write:products',
'read:orders',
'read:own-orders',
'write:own-profile'
],
[ROLES.ADMIN]: [
'read:products', 'write:products', 'delete:products',
'read:orders', 'write:orders', 'delete:orders',
'read:users', 'write:users', 'delete:users',
'read:own-orders','write:own-profile'
]
};
function hasPermission(role, permission) {
return PERMISSIONS[role]?.includes(permission) ?? false;
}
module.exports = { ROLES, PERMISSIONS, hasPermission };The authorize Middleware
The authorize middleware factory takes a required permission as an argument and returns a middleware function. It runs after the authenticate middleware (which sets req.user), reads the user's role from the JWT payload, and checks it against the permission map. If the check passes, it calls next(); otherwise it returns 403 Forbidden.
// middleware/authorize.js
const { hasPermission } = require('../config/roles');
function authorize(permission) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthenticated' });
}
if (!hasPermission(req.user.role, permission)) {
return res.status(403).json({
error: 'Forbidden',
required: permission,
yourRole: req.user.role
});
}
next();
};
}
module.exports = authorize;
// Using it in routes
const authenticate = require('../middleware/authenticate');
const authorize = require('../middleware/authorize');
router.get('/products', authenticate, authorize('read:products'), listProducts);
router.post('/products', authenticate, authorize('write:products'), createProduct);
router.delete('/products/:id', authenticate, authorize('delete:products'), deleteProduct);
router.get('/users', authenticate, authorize('read:users'), listUsers);