Definition
Authorization model where permissions are assigned to roles, and roles are assigned to users. Users inherit all permissions from their assigned roles. Simplifies permission management by grouping related permissions into meaningful job functions.
Example
Role “Editor” has permissions [read:articles, write:articles, publish:articles]. Role “Viewer” has [read:articles]. User Alice assigned “Editor” role inherits all three permissions. User Bob assigned “Viewer” can only read.
Analogy
Like job titles in a company. A “Manager” role has certain responsibilities and access (approve expenses, view salaries). Anyone hired as Manager automatically gets those privileges. When they leave that role, they lose them. Much easier than tracking individual permissions for each employee.
Code Example
// RBAC Implementation
class RBACManager {
constructor() {
// Define roles and their permissions
this.roles = {
'admin': {
permissions: ['*:*'], // Superuser
description: 'Full system access'
},
'editor': {
permissions: [
'read:articles',
'write:articles',
'publish:articles',
'read:comments',
'moderate:comments'
],
description: 'Content editor'
},
'author': {
permissions: [
'read:articles',
'write:own-articles',
'read:comments'
],
description: 'Content author'
},
'viewer': {
permissions: [
'read:articles',
'read:comments'
],
description: 'Read-only access'
},
'moderator': {
permissions: [
'read:*',
'delete:comments',
'ban:users'
],
description: 'Community moderator'
}
}
// Role hierarchy (optional)
this.hierarchy = {
'admin': ['editor', 'moderator', 'author', 'viewer'],
'editor': ['author', 'viewer'],
'author': ['viewer']
}
}
// Check if user has required role
hasRole(user, requiredRole) {
if (!user.roles) return false
return user.roles.includes(requiredRole)
}
// Check if user has any of required roles
hasAnyRole(user, requiredRoles) {
if (!user.roles) return false
return requiredRoles.some(role => user.roles.includes(role))
}
// Get all permissions for a user based on their roles
getUserPermissions(user) {
const permissions = new Set()
if (!user.roles) return permissions
user.roles.forEach(roleName => {
const role = this.roles[roleName]
if (role) {
role.permissions.forEach(perm => permissions.add(perm))
}
// Include inherited permissions via hierarchy
const inheritedRoles = this.hierarchy[roleName] || []
inheritedRoles.forEach(inheritedRole => {
const inherited = this.roles[inheritedRole]
if (inherited) {
inherited.permissions.forEach(perm => permissions.add(perm))
}
})
})
return permissions
}
// Check if user has specific permission via their roles
hasPermission(user, requiredPermission) {
const userPermissions = this.getUserPermissions(user)
// Check direct match
if (userPermissions.has(requiredPermission)) {
return true
}
// Check wildcard permissions
const [action, resource] = requiredPermission.split(':')
return (
userPermissions.has('*:*') ||
userPermissions.has(`${action}:*`) ||
userPermissions.has(`*:${resource}`)
)
}
// Assign role to user (with validation)
assignRole(user, roleName) {
if (!this.roles[roleName]) {
throw new Error(`Invalid role: ${roleName}`)
}
if (!user.roles) {
user.roles = []
}
if (!user.roles.includes(roleName)) {
user.roles.push(roleName)
}
}
// Remove role from user
removeRole(user, roleName) {
if (user.roles) {
user.roles = user.roles.filter(role => role !== roleName)
}
}
}
// Middleware for role-based authorization
const rbac = new RBACManager()
function requireRole(roleName) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' })
}
if (!rbac.hasRole(req.user, roleName)) {
return res.status(403).json({
error: 'Forbidden',
message: `Required role: ${roleName}`
})
}
next()
}
}
function requireAnyRole(...roleNames) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' })
}
if (!rbac.hasAnyRole(req.user, roleNames)) {
return res.status(403).json({
error: 'Forbidden',
message: `Required one of: ${roleNames.join(', ')}`
})
}
next()
}
}
// Usage in routes
app.get('/api/articles',
requireAnyRole('viewer', 'author', 'editor', 'admin'),
async (req, res) => {
const articles = await db.articles.findAll()
res.json(articles)
}
)
app.post('/api/articles',
requireRole('editor'),
async (req, res) => {
const article = await db.articles.create(req.body)
res.json(article)
}
)
app.delete('/api/users/:id',
requireRole('admin'),
async (req, res) => {
await db.users.delete(req.params.id)
res.status(204).send()
}
)
// Dynamic RBAC with database
class DynamicRBAC {
async getRolePermissions(roleName) {
// Fetch from database instead of hardcoded
const role = await db.roles.findOne({ where: { name: roleName }})
return role?.permissions || []
}
async createRole(name, permissions, description) {
return await db.roles.create({
name,
permissions,
description,
createdAt: new Date()
})
}
async updateRolePermissions(roleName, permissions) {
await db.roles.update(
{ permissions },
{ where: { name: roleName }}
)
// Invalidate permission cache
await this.invalidatePermissionCache(roleName)
}
async assignRoleToUser(userId, roleName) {
await db.userRoles.create({
userId,
roleName,
assignedAt: new Date()
})
// Audit log
await auditLog.log({
action: 'role_assigned',
userId,
roleName,
timestamp: new Date()
})
}
}
Security Notes
CRITICAL: RBAC (Role-Based Access Control) assigns permissions via roles. Simple but less flexible than ABAC.
RBAC Components:
- Roles: Groups of permissions (admin, user, guest)
- Users: Assigned to roles
- Permissions: Specific capabilities per role
- Resource: What can be accessed
Implementation:
- Assign roles: Assign users to roles
- Role inheritance: Roles can inherit from other roles
- Dynamic roles: Roles assigned based on context
- Multiple roles: Users can have multiple roles
Security:
- Role separation: Prevent conflicting roles
- Audit: Log role assignments
- Review: Regularly review role assignments
- Least privilege: Minimal permissions per role
Advantages:
- Simple: Easy to understand and implement
- Scalable: Easy to manage large user bases
- Auditable: Clear roles for compliance
Limitations:
- Inflexible: Cannot express complex rules
- Coarse-grained: Cannot control individual fields
- Static: Roles defined upfront
- For context: Cannot consider context (time, location)