Authorization Levels
Implement role-based access control (RBAC) to manage user permissions. Create admin, author, and reader roles with different access levels.
๐ฏ What You'll Learn
- โขImplement role-based access control (RBAC)
- โขCreate permission decorators and middleware
- โขBuild admin, author, and reader permission systems
- โขSecure endpoints with role requirements
Authorization Levels
Welcome to Role-Based Access Control (RBAC)! In this lesson, you'll implement a sophisticated permission system that controls what users can do based on their roles.
๐ฏ What We're Building
Your authorization system will provide:
- ๐ Admin users with full system access
- โ๏ธ Author users who can create and manage content
- ๐ Reader users with read-only access
- ๐ Permission decorators to protect endpoints
- ๐ก๏ธ Resource ownership validation
๐๏ธ RBAC Fundamentals
What is Role-Based Access Control?
RBAC is a security model that restricts access based on user roles:
- Roles: Job functions (Admin, Author, Reader)
- Permissions: Specific actions (create_post, manage_users)
- Users: Individuals assigned to roles
- Resources: Things being protected (posts, user profiles)
Role Hierarchy
Admin (Full Access)
โโโ Can manage users
โโโ Can access admin panel
โโโ Can edit/delete any content
โโโ Inherits all Author + Reader permissions
Author (Content Creator)
โโโ Can create posts
โโโ Can edit own posts
โโโ Can publish posts
โโโ Inherits all Reader permissions
Reader (Basic Access)
โโโ Can read posts
โโโ Can update own profile
โโโ Basic access only
๐จ Permission Architecture
1. Permission Manager
class PermissionManager:
def __init__(self):
# Define role hierarchy
self.role_hierarchy = {
UserRole.ADMIN: [UserRole.AUTHOR, UserRole.READER],
UserRole.AUTHOR: [UserRole.READER],
UserRole.READER: []
}
# Define role-specific permissions
self.permissions = {
UserRole.READER: ['read_posts', 'update_own_profile'],
UserRole.AUTHOR: ['create_post', 'update_own_post'],
UserRole.ADMIN: ['manage_users', 'access_admin_panel']
}
2. Authorization Decorators
Protect functions with role/permission requirements:
@require_role("admin")
async def admin_function():
return "Admin only content"
@require_permission("create_post")
async def create_post():
return "Can create posts"
@require_ownership_or_admin()
async def edit_post(post_id: int, current_user: dict):
# User can edit their own posts, admin can edit any
return f"Editing post {post_id}"
3. FastAPI Integration
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer
security = HTTPBearer()
async def get_current_user(token: str = Depends(security)):
user = await auth_service.get_current_user(token.credentials)
if not user:
raise HTTPException(401, "Invalid token")
return user
async def require_admin(current_user: dict = Depends(get_current_user)):
if current_user['role'] != 'admin':
raise HTTPException(403, "Admin access required")
return current_user
@app.get("/admin/users")
async def get_users(admin_user: dict = Depends(require_admin)):
return await get_all_users()
๐ Security Patterns
1. Principle of Least Privilege
Give users only the minimum access needed:
# โ
Good: Specific permissions
@require_permission("read_own_posts")
async def get_my_posts():
pass
# โ Bad: Overly broad access
@require_role("admin") # Too much for reading own posts
async def get_my_posts():
pass
2. Defense in Depth
Multiple layers of security:
@app.put("/posts/{post_id}")
@require_permission("update_post") # Layer 1: General permission
async def update_post(
post_id: int,
post_data: PostUpdate,
current_user: dict = Depends(get_current_user) # Layer 2: Authentication
):
# Layer 3: Ownership check
post = await get_post(post_id)
if post.author_id != current_user['id'] and current_user['role'] != 'admin':
raise HTTPException(403, "Can only edit your own posts")
return await update_post_in_db(post_id, post_data)
3. Fail-Safe Defaults
Default to denial when unsure:
def can_access_resource(self, user_role: str, permission: str) -> bool:
# Default to False if anything is unclear
if not user_role or not permission:
return False
if user_role not in self.valid_roles:
return False
return permission in self.get_user_permissions(user_role)
๐ ๏ธ Implementation Patterns
1. Decorator Factory Pattern
Create reusable decorators:
def require_role(required_role: str):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
current_user = get_current_user()
if not has_role_or_higher(current_user['role'], required_role):
raise Exception(f"Role '{required_role}' required")
return await func(*args, **kwargs)
return wrapper
return decorator
# Usage
@require_role("author")
async def create_post():
pass
2. Context-Aware Authorization
Check permissions based on context:
def require_ownership_or_admin(resource_field: str = 'author_id'):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
current_user = get_current_user()
# Admin bypass
if current_user['role'] == 'admin':
return await func(*args, **kwargs)
# Check ownership
resource_owner_id = kwargs.get(resource_field)
if resource_owner_id != current_user['id']:
raise Exception("Access denied")
return await func(*args, **kwargs)
return wrapper
return decorator
3. Permission Inheritance
Higher roles inherit lower permissions:
def get_user_permissions(self, user_role: str) -> List[str]:
permissions = set()
# Add direct permissions
permissions.update(self.permissions.get(user_role, []))
# Add inherited permissions
for inherited_role in self.role_hierarchy.get(user_role, []):
permissions.update(self.permissions.get(inherited_role, []))
return list(permissions)
๐งช Testing Authorization
1. Unit Tests for Permissions
def test_permission_hierarchy():
pm = PermissionManager()
# Admin should have all permissions
admin_perms = pm.get_user_permissions('admin')
author_perms = pm.get_user_permissions('author')
assert len(admin_perms) > len(author_perms)
assert 'create_post' in admin_perms # Inherited from author
assert 'read_posts' in admin_perms # Inherited from reader
def test_decorator_access():
@require_role('admin')
async def admin_func():
return 'success'
# Test with admin user
set_current_user({'role': 'admin'})
result = await admin_func()
assert result == 'success'
# Test with regular user
set_current_user({'role': 'reader'})
with pytest.raises(Exception):
await admin_func()
2. Integration Tests
async def test_endpoint_security():
# Test admin endpoint
response = await client.get('/admin/users',
headers={'Authorization': 'Bearer admin_token'})
assert response.status_code == 200
# Test with insufficient permissions
response = await client.get('/admin/users',
headers={'Authorization': 'Bearer reader_token'})
assert response.status_code == 403
๐ก Best Practices
1. Clear Permission Names
# โ
Clear and specific
'create_post', 'update_own_post', 'delete_any_post'
# โ Vague or confusing
'post_access', 'manage_content', 'admin_stuff'
2. Audit Trail
Log authorization decisions:
async def check_permission(user_id: int, permission: str) -> bool:
result = has_permission(user_id, permission)
# Log the authorization check
logger.info(f"User {user_id} permission check: {permission} = {result}")
return result
3. Graceful Degradation
Provide meaningful error messages:
@require_role("author")
async def create_post():
pass
# Instead of generic "Access Denied"
# Provide: "This action requires Author role or higher. You have Reader role."
๐จ Common Pitfalls
1. Role Confusion
# โ Don't check roles directly everywhere
if user.role == 'admin':
allow_access()
# โ
Check permissions instead
if has_permission(user, 'manage_users'):
allow_access()
2. Missing Edge Cases
# โ Assumes user exists
def check_permission(user, permission):
return permission in user.permissions # Crashes if user is None
# โ
Handle edge cases
def check_permission(user, permission):
if not user or not permission:
return False
return permission in user.get('permissions', [])
3. Inconsistent Enforcement
# โ Some endpoints protected, others not
@app.get("/posts") # No protection
async def get_posts(): pass
@app.post("/posts")
@require_permission("create_post")
async def create_post(): pass
# โ
Consistent protection strategy
@app.get("/posts")
@require_permission("read_posts")
async def get_posts(): pass
๐ฏ Your Implementation Tasks
- Create PermissionManager with role hierarchy
- Build authorization decorators for functions
- Implement AuthorizationService with token integration
- Test all permission combinations thoroughly
- Handle edge cases and error scenarios
๐ Real-World Applications
After mastering RBAC, you can apply it to:
- Multi-tenant applications (organization-based roles)
- Microservices (service-to-service permissions)
- API gateways (centralized authorization)
- Content management (editor/reviewer workflows)
Ready to secure your blog API with bulletproof authorization? Let's build it! ๐
๐ก Hint
Start by defining the role hierarchy - admins inherit author permissions, authors inherit reader permissions. Use decorators to protect functions and check user roles/permissions before allowing access.
Ready to Practice?
Now that you understand the theory, let's put it into practice with hands-on coding!
Start Interactive Lesson