Role-Based Access Control
CMS API implements RBAC through a many-to-many relationship between users, roles, and permissions.
Model
- A user can have multiple roles
- A role can have multiple permissions
- A user's effective permissions are the union of all permissions across all their roles
Permission Naming
Permissions follow a resource:action pattern:
This makes it easy to reason about what each permission allows and to group permissions by resource.
How Authorization Works
When a user logs in, their permissions are collected and embedded in the access token:
# Collected at login time
permissions = []
for role in user.roles:
for permission in role.permissions:
permissions.append(permission.name)
# Embedded in JWT
payload = {
"sub": user_id,
"permissions": ["users:read", "users:update", ...],
...
}
On every request, the token is decoded and permissions are checked without hitting the database:
def require_permission(permission: str):
def dependency(current_user = Depends(get_current_active_user)):
if not current_user.has_permission(permission):
raise ForbiddenException(f"Missing permission: {permission}")
return current_user
return dependency
Protecting Routes
By permission
@router.delete("/{id}", dependencies=[Depends(require_permission("users:delete"))])
def delete_user(id: UUID, service = Depends(get_user_service)):
return service.delete(id)
By role
Just authenticated
Default Roles
| Role | Description |
|---|---|
admin |
Full access to all resources |
manager |
Read/create/update users, assign roles, read roles and permissions |
viewer |
Read-only access to all resources |
Permission Reference
| Permission | Description |
|---|---|
users:read |
View users |
users:create |
Create users |
users:update |
Update users |
users:delete |
Delete users |
users:assign_roles |
Assign roles to users |
roles:read |
View roles |
roles:create |
Create roles |
roles:update |
Update roles |
roles:delete |
Delete roles |
permissions:read |
View permissions |
permissions:create |
Create permissions |
permissions:update |
Update permissions |
permissions:delete |
Delete permissions |
Adding New Permissions
When you add a new resource to the API:
1. Create the permission via API or seed
2. Assign it to the appropriate roles
3. Protect your route
@router.get("/reports", dependencies=[Depends(require_permission("reports:read"))])
def get_reports():
...
Info
After adding permissions to roles, users must log in again to receive updated tokens with the new permissions.