Implementing Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC)
Mastering authorization architecture in Node.js by implementing Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC) with Express middleware.
As web applications grow in complexity, securing endpoints requires more than simple authentication (verifying *who* a user is). Authorization—determining *what* an authenticated user is allowed to do—becomes the backbone of application security. Without a robust authorization model, applications risk horizontal and vertical privilege escalation vulnerabilities.
Two of the most widely adopted authorization patterns are **Role-Based Access Control (RBAC)**, which grants permissions based on assigned user roles, and **Attribute-Based Access Control (ABAC)**, which evaluates dynamic attributes such as resource ownership, time of day, and environmental context. This comprehensive guide explores how to design and implement both authorization paradigms in Node.js and Express using custom TypeScript middleware.
Role-Based Access Control (RBAC) in Express
RBAC simplifies permission management by grouping permissions into roles (e.g., `admin`, `moderator`, `user`). Users are then assigned one or more roles, and endpoints check whether the user's role satisfies the required privilege level.
import { Request, Response, NextFunction } from 'express';
export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}
export interface AuthenticatedRequest extends Request {
user?: {
id: string;
role: UserRole;
};
}
export function requireRole(allowedRoles: UserRole[]) {
return (req: AuthenticatedRequest, res: Response, next: NextFunction): void => {
const user = req.user;
if (!user) {
res.status(401).json({ success: false, message: 'Unauthorized: No user session found.' });
return;
}
if (!allowedRoles.includes(user.role)) {
res.status(403).json({ success: false, message: 'Forbidden: Insufficient permissions.' });
return;
}
next();
};
}
Attribute-Based Access Control (ABAC) for Granular Security
While RBAC works well for broad functional roles, it often falls short when access decisions depend on dynamic context—such as ensuring a user can only edit their own profile or blog post.
ABAC evaluates rules combining user attributes, resource attributes, action types, and environmental conditions.
import { Response, NextFunction } from 'express';
import { AuthenticatedRequest } from './rbac';
interface Resource {
userId: string;
isPublished: boolean;
}
export function verifyResourceOwnership(fetchResource: (req: AuthenticatedRequest) => Promise<Resource | null>) {
return async (req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> => {
try {
const user = req.user;
if (!user) {
res.status(401).json({ success: false, message: 'Unauthorized' });
return;
}
const resource = await fetchResource(req);
if (!resource) {
res.status(404).json({ success: false, message: 'Resource not found' });
return;
}
// ABAC Condition: User must own the resource OR be an admin
const isOwner = resource.userId === user.id;
const isAdmin = user.role === 'admin';
if (!isOwner && !isAdmin) {
res.status(403).json({ success: false, message: 'Forbidden: You do not own this resource.' });
return;
}
next();
} catch (error: any) {
res.status(500).json({ success: false, message: error.message });
}
};
}
Summary
Implementing Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC) in Node.js provides a robust authorization foundation for modern backend applications.
By combining role verification middleware for broad endpoint protection with attribute-based resource ownership checks for granular data security, engineering teams can build scalable, secure, and easily maintainable APIs.