Role-Based Access Control (RBAC): Implementing Secure Authorization
Whitespots Team ·
rbac
authorization
access-control
permissions
Introduction
Role-Based Access Control (RBAC) is essential for managing user permissions at scale. This guide covers implementing flexible, maintainable RBAC systems with practical examples.
RBAC Database Schema
sql-- Roles table CREATE TABLE roles ( id SERIAL PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Permissions table CREATE TABLE permissions ( id SERIAL PRIMARY KEY, resource VARCHAR(50) NOT NULL, action VARCHAR(50) NOT NULL, description TEXT, UNIQUE(resource, action) ); -- Role-Permission mapping CREATE TABLE role_permissions ( role_id INTEGER REFERENCES roles(id), permission_id INTEGER REFERENCES permissions(id), PRIMARY KEY (role_id, permission_id) ); -- User-Role mapping CREATE TABLE user_roles ( user_id INTEGER REFERENCES users(id), role_id INTEGER REFERENCES roles(id), PRIMARY KEY (user_id, role_id) );
RBAC Implementation
javascript// Permission checking middleware async function checkPermission(resource, action) { return async (req, res, next) => { const userId = req.user.id; const hasPermission = await db.query(` SELECT EXISTS ( SELECT 1 FROM user_roles ur JOIN role_permissions rp ON ur.role_id = rp.role_id JOIN permissions p ON rp.permission_id = p.id WHERE ur.user_id = $1 AND p.resource = $2 AND p.action = $3 ) `, [userId, resource, action]); if (hasPermission.rows[0].exists) { next(); } else { res.status(403).json({ error: 'Insufficient permissions' }); } }; } // Usage app.get('/api/users', authenticate, checkPermission('users', 'read'), getUsersHandler ); app.post('/api/users', authenticate, checkPermission('users', 'create'), createUserHandler ); app.delete('/api/users/:id', authenticate, checkPermission('users', 'delete'), deleteUserHandler );
Role Management
javascript// Create role with permissions async function createRole(name, permissionIds) { const role = await db.roles.create({ name }); await db.rolePermissions.insertMany( permissionIds.map(permId => ({ roleId: role.id, permissionId: permId })) ); return role; } // Assign role to user async function assignRole(userId, roleId) { await db.userRoles.create({ userId, roleId }); } // Check if user has specific permission async function hasPermission(userId, resource, action) { const result = await db.query(` SELECT EXISTS ( SELECT 1 FROM user_roles ur JOIN role_permissions rp ON ur.role_id = rp.role_id JOIN permissions p ON rp.permission_id = p.id WHERE ur.user_id = $1 AND p.resource = $2 AND p.action = $3 ) `, [userId, resource, action]); return result.rows[0].exists; }
Hierarchical Roles
javascript// Role hierarchy const roleHierarchy = { admin: ['moderator', 'user'], moderator: ['user'], user: [] }; async function hasPermissionWithHierarchy(userId, resource, action) { const userRoles = await getUserRoles(userId); // Get all inherited roles const allRoles = new Set(); function addRoleAndChildren(role) { allRoles.add(role); (roleHierarchy[role] || []).forEach(addRoleAndChildren); } userRoles.forEach(addRoleAndChildren); // Check permissions for all roles const hasAccess = await db.query(` SELECT EXISTS ( SELECT 1 FROM role_permissions rp JOIN permissions p ON rp.permission_id = p.id JOIN roles r ON rp.role_id = r.id WHERE r.name = ANY($1) AND p.resource = $2 AND p.action = $3 ) `, [Array.from(allRoles), resource, action]); return hasAccess.rows[0].exists; }
Resource-Level Permissions
javascript// Check ownership or admin access async function canAccessResource(userId, resourceId, resource, action) { // Check if user owns the resource const isOwner = await checkOwnership(userId, resource, resourceId); if (isOwner) { return true; } // Check if user has admin permission return hasPermission(userId, resource, action); } // Middleware for resource access function checkResourceAccess(resourceType) { return async (req, res, next) => { const resourceId = req.params.id; const action = req.method.toLowerCase(); const canAccess = await canAccessResource( req.user.id, resourceId, resourceType, action ); if (canAccess) { next(); } else { res.status(403).json({ error: 'Access denied' }); } }; } // Usage app.put('/api/posts/:id', authenticate, checkResourceAccess('posts'), updatePostHandler );
RBAC Best Practices
- ✅ Use least privilege principle
- ✅ Separate roles from permissions
- ✅ Implement role hierarchies
- ✅ Cache permission checks
- ✅ Audit permission changes
- ✅ Regular permission reviews
- ✅ Resource-level access control
- ✅ Default deny approach
- ✅ Document role structures
- ✅ Test authorization logic
Conclusion
RBAC provides scalable, maintainable authorization by separating roles from permissions. Implement proper role hierarchies, resource-level controls, and regular audits for effective access management.


