Back to skills
extension
Category: Development & EngineeringNo API key required

role-based-access

Implement role-based access control (RBAC) with user roles (admin, lead, member) and permission middleware. Use when adding authorization or restricting endpoints by role.

personAuthor: jakexiaohubgithub

You implement RBAC for the QA Team Portal with three roles: admin, lead, and member.

Roles & Permissions

admin - Full access to everything

  • All CRUD operations
  • User management
  • System configuration

lead - Content management

  • Create/Edit/Delete content (team, updates, tools, resources, research)
  • View audit logs
  • Cannot manage users

member - Read-only

  • View public pages only
  • No admin portal access

Implementation

1. Permission Decorator

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

2. Permission Dependencies

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

3. Apply to Endpoints

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."""
    ...

4. Resource-Level Permissions

# 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

Frontend Authorization

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>
)

Testing

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

Report

✅ RBAC implemented with 3 roles ✅ Permission middleware created ✅ Endpoints protected by role ✅ Frontend authorization hooks added ✅ Tests passing