What Is RBAC?
Role-Based Access Control (RBAC) is an authorization model where permissions are assigned to roles, and users are assigned to those roles. Instead of managing permissions for each user individually, you manage roles and their associated permissions, then assign users to appropriate roles.
Core RBAC Concepts
| Concept | Definition | Example |
|---------|------------|---------|
| User | An individual account | alice@example.com |
| Role | A collection of permissions | Editor, Admin, Viewer |
| Permission | An action on a resource | article:create, article:delete |
| Resource | The object being accessed | Article, User, Comment |
| Session | A user's active role mapping | Alice as Editor |
The RBAC Model
Level 1: Flat RBAC
Users are assigned roles, and roles have permissions. This simple structure works for most applications.
Users ──< Role Assignment >── Roles ──< Permission Assignment >── Permissions
Level 2: Hierarchical RBAC
Roles can inherit permissions from other roles. For example, an Admin role inherits all Editor permissions plus additional administrative permissions.
Admin ──inherits──> Editor ──inherits──> Viewer
Database Schema
-- Core RBAC tables
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL,
description TEXT
);
CREATE TABLE permissions (
id SERIAL PRIMARY KEY,
resource VARCHAR(50) NOT NULL,
action VARCHAR(20) NOT NULL,
UNIQUE(resource, action)
);
CREATE TABLE role_permissions (
role_id INTEGER REFERENCES roles(id),
permission_id INTEGER REFERENCES permissions(id),
PRIMARY KEY (role_id, permission_id)
);
CREATE TABLE user_roles (
user_id INTEGER REFERENCES users(id),
role_id INTEGER REFERENCES roles(id),
granted_by INTEGER REFERENCES users(id),
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, role_id)
);
-- For hierarchical RBAC
CREATE TABLE role_hierarchy (
parent_role_id INTEGER REFERENCES roles(id),
child_role_id INTEGER REFERENCES roles(id),
PRIMARY KEY (parent_role_id, child_role_id)
);
Implementation Patterns
Node.js / Express Middleware
// Permission definition
const PERMISSIONS = {
ARTICLE: {
CREATE: 'article:create',
READ: 'article:read',
UPDATE: 'article:update',
DELETE: 'article:delete',
PUBLISH: 'article:publish'
},
USER: {
LIST: 'user:list',
MANAGE: 'user:manage'
}
};
// Role definitions with permission inheritance
const ROLES = {
viewer: {
permissions: ['article:read'],
inherits: []
},
editor: {
permissions: ['article:create', 'article:update'],
inherits: ['viewer']
},
admin: {
permissions: [
'article:delete', 'article:publish',
'user:list', 'user:manage'
],
inherits: ['editor']
}
};
// Authorization middleware
function authorize(...requiredPermissions) {
return (req, res, next) => {
const userRoles = req.user.roles;
const userPermissions = getUserPermissions(userRoles);
const hasPermission = requiredPermissions.every(
p => userPermissions.includes(p)
);
if (!hasPermission) {
return res.status(403).json({
error: 'Insufficient permissions'
});
}
next();
};
}
// Usage in routes
app.post('/api/articles',
authenticate,
authorize(PERMISSIONS.ARTICLE.CREATE),
createArticle
);
app.delete('/api/articles/:id',
authenticate,
authorize(PERMISSIONS.ARTICLE.DELETE),
deleteArticle
);
Python / FastAPI
from enum import Enum
from functools import wraps
from fastapi import HTTPException, Depends
class Permission(Enum):
ARTICLE_CREATE = "article:create"
ARTICLE_READ = "article:read"
ARTICLE_UPDATE = "article:update"
ARTICLE_DELETE = "article:delete"
USER_MANAGE = "user:manage"
ROLE_PERMISSIONS = {
"viewer": {Permission.ARTICLE_READ},
"editor": {
Permission.ARTICLE_READ,
Permission.ARTICLE_CREATE,
Permission.ARTICLE_UPDATE,
},
"admin": {
Permission.ARTICLE_READ,
Permission.ARTICLE_CREATE,
Permission.ARTICLE_UPDATE,
Permission.ARTICLE_DELETE,
Permission.USER_MANAGE,
},
}
def require_permission(permission: Permission):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
user = kwargs.get('current_user')
if not user:
raise HTTPException(status_code=401)
user_perms = ROLE_PERMISSIONS.get(user.role, set())
if permission not in user_perms:
raise HTTPException(
status_code=403,
detail="Insufficient permissions"
)
return await func(*args, **kwargs)
return wrapper
return decorator
# Usage
@app.delete("/articles/{article_id}")
@require_permission(Permission.ARTICLE_DELETE)
async def delete_article(article_id: int, current_user = Depends(get_current_user)):
# Delete logic
pass
Attribute-Based Access Control (ABAC)
For more granular control, extend RBAC with ABAC policies that consider resource attributes and context:
def can_access_resource(user, resource, action):
"""Check RBAC first, then ABAC policies."""
# RBAC check
if not has_role_permission(user.role, action):
return False
# ABAC policies
policies = {
# Users can edit their own articles
('article:update', 'article'): lambda u, r: r.author_id == u.id,
# Admins can edit any article
('article:update', 'article'): lambda u, r: 'admin' in u.roles,
# Only article authors can delete
('article:delete', 'article'): lambda u, r: r.author_id == u.id,
}
policy = policies.get((action, resource.type))
if policy:
return policy(user, resource)
return False
Performance Optimization
Cache resolved permissions to avoid repeated database queries:
import redis
import json
r = redis.Redis()
def get_cached_permissions(user_id):
cache_key = f"perms:{user_id}"
cached = r.get(cache_key)
if cached:
return json.loads(cached)
permissions = resolve_user_permissions(user_id)
r.setex(cache_key, 300, json.dumps(permissions)) # 5 min TTL
return permissions
Audit Logging
Every authorization decision should be logged:
def log_authorization(user_id, action, resource, granted, reason=""):
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"user_id": user_id,
"action": action,
"resource": resource,
"granted": granted,
"reason": reason,
"source_ip": request.remote_addr
}
audit_logger.info(json.dumps(log_entry))
Common Pitfalls
Summary
RBAC simplifies authorization by grouping permissions into roles and assigning roles to users. Start with flat RBAC and add hierarchy as needed. Always check permissions rather than role names, cache resolved permissions for performance, and layer on ABAC policies for fine-grained access control. Log all authorization decisions for auditability.