Implement role-based access control (RBAC) with user roles (admin, lead, member) and permission middleware. Use when adding authorization or restricting endpoints by role.
You implement RBAC for the QA Team Portal with three roles: admin, lead, and member.
admin - Full access to everything
lead - Content management
member - Read-only
Location: backend/app/core/permissions.py
from functools import wraps
from fastapi import HTTPException
from app.models.user import User
def require_role(*allowed_roles: str):
"""
Decorator to require specific roles.
Usage:
@require_role("admin", "lead")
async def update_team_member(...):
...
"""
def decorator(func):
@wraps(func)
async def wrapper(*args, current_user: User = None, **kwargs):
if not current_user:
raise HTTPException(401, "Authentication required")
if current_user.role not in allowed_roles:
raise HTTPException(
403,
f"Requires role: {' or '.join(allowed_roles)}"
)
return await func(*args, current_user=current_user, **kwargs)
return wrapper
return decorator
Location: backend/app/api/deps.py
async def get_current_admin(
current_user: User = Depends(get_current_user)
) -> User:
"""Require admin role."""
if current_user.role != "admin":
raise HTTPException(403, "Admin access required")
return current_user
async def get_current_lead_or_admin(
current_user: User = Depends(get_current_user)
) -> User:
"""Require lead or admin role."""
if current_user.role not in ["admin", "lead"]:
raise HTTPException(403, "Lead or admin access required")
return current_user
Location: backend/app/api/v1/endpoints/team_members.py
from app.api.deps import get_current_user, get_current_lead_or_admin
@router.get("/team-members")
async def get_team_members():
"""Public endpoint - no auth required."""
...
@router.post("/admin/team-members")
async def create_team_member(
data: TeamMemberCreate,
current_user: User = Depends(get_current_lead_or_admin)
):
"""Lead or admin can create team members."""
...
@router.delete("/admin/team-members/{id}")
async def delete_team_member(
id: UUID,
current_user: User = Depends(get_current_admin)
):
"""Only admin can delete."""
...
# Check if user can modify specific resource
async def can_modify_resource(
user: User,
resource_id: UUID,
db: Session
) -> bool:
"""Check if user can modify resource."""
if user.role == "admin":
return True # Admin can modify anything
# Lead can only modify their own resources
if user.role == "lead":
resource = await crud.resource.get(db, id=resource_id)
return resource and resource.created_by == user.id
return False # Members cannot modify
Location: frontend/src/hooks/useAuth.ts
export const useAuth = () => {
const { user } = useContext(AuthContext)
const hasRole = (...roles: string[]) => {
return user && roles.includes(user.role)
}
const isAdmin = () => hasRole('admin')
const isLead = () => hasRole('lead', 'admin')
const isMember = () => hasRole('member', 'lead', 'admin')
return {
user,
hasRole,
isAdmin,
isLead,
isMember
}
}
Usage in Components:
const { isAdmin, isLead } = useAuth()
return (
<div>
{isLead() && (
<Button onClick={handleEdit}>Edit</Button>
)}
{isAdmin() && (
<Button onClick={handleDelete}>Delete</Button>
)}
</div>
)
def test_admin_can_delete(client, admin_token):
response = client.delete(
"/api/v1/admin/team-members/123",
headers={"Authorization": f"Bearer {admin_token}"}
)
assert response.status_code == 204
def test_lead_cannot_delete(client, lead_token):
response = client.delete(
"/api/v1/admin/team-members/123",
headers={"Authorization": f"Bearer {lead_token}"}
)
assert response.status_code == 403
def test_member_cannot_access_admin(client, member_token):
response = client.get(
"/api/v1/admin/dashboard",
headers={"Authorization": f"Bearer {member_token}"}
)
assert response.status_code == 403
ā RBAC implemented with 3 roles ā Permission middleware created ā Endpoints protected by role ā Frontend authorization hooks added ā Tests passing