Checking permissions by comparing strings in an array works fine until you have a few dozen of them — then every check becomes an array scan. Bitmasks fix that. Here's how I set one up.
1. Defining the Matrix
We map operations to integers or bitwise values:
const PERMISSIONS = {
READ_LEADS: 1 << 0, // 1
WRITE_LEADS: 1 << 1, // 2
DELETE_LEADS: 1 << 2, // 4
MANAGE_USERS: 1 << 3 // 8
};
This allows roles to be defined as numeric sums (bitmasks). A manager who can read and write leads gets a score of 1 + 2 = 3 (binary 0011). An admin gets 15 (binary 1111).
2. Fast Permission Verification
Using bitwise AND (&) makes evaluation instantaneous:
function hasPermission(userRoleMask: number, requiredPermission: number): boolean {
return (userRoleMask & requiredPermission) === requiredPermission;
}
// Evaluation:
// 3 (Manager: 0011) & 2 (Write leads: 0010) === 2 -> true!
// 3 (Manager: 0011) & 8 (Manage users: 1000) === 8 -> false!
3. Integrating Express.js Middleware
Create a reusable middleware:
export function requirePermission(permission: number) {
return async (req: Request, res: Response, next: NextFunction) => {
const userRoleMask = req.user?.roleMask;
if (!userRoleMask || !hasPermission(userRoleMask, permission)) {
return res.status(403).json({ error: 'Access Denied: Insufficient Permissions' });
}
next();
};
}
It's a small pattern, but it keeps permission checks off the hot path entirely — just a bitwise AND, no array scans, no extra database round-trip.
