FastAPI RBAC


What is Role-Based Access Control (RBAC)?

Role-Based Access Control (RBAC) is a method of restricting access to resources based on the roles assigned to users within an organization. Each role has a set of permissions associated with it, and users with specific roles are allowed to access certain endpoints or perform specific actions. In FastAPI, RBAC can be implemented by checking the user's role when handling requests, ensuring that only authorized users have access to certain routes or actions.


How do you implement basic RBAC in FastAPI?

To implement basic RBAC in FastAPI, you assign roles to users and check those roles in route functions before granting access. You can store the user's role in the JWT token and verify the role in your route handler to ensure the user has the required permissions for accessing the resource.

Example of implementing basic RBAC:

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import jwt

SECRET_KEY = "mysecret"
ALGORITHM = "HS256"

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def verify_token(token: str, required_role: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        role = payload.get("role")
        if role is None or role != required_role:
            raise HTTPException(status_code=403, detail="Insufficient permissions")
    except jwt.JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.get("/admin/")
async def read_admin_data(token: str = Depends(oauth2_scheme)):
    verify_token(token, "admin")
    return {"message": "Welcome, admin!"}

@app.get("/user/")
async def read_user_data(token: str = Depends(oauth2_scheme)):
    verify_token(token, "user")
    return {"message": "Welcome, user!"}

In this example, users must have the appropriate role ("admin" or "user") to access specific routes. The verify_token function decodes the JWT, checks the user's role, and raises an HTTPException if the role does not match the required role for the route.


How do you include roles in JWT tokens for RBAC in FastAPI?

To implement RBAC with JWT tokens in FastAPI, you can include the user's role as a claim in the token when the token is generated. This allows you to easily check the user's role when verifying the token.

Example of including roles in JWT tokens:

from datetime import datetime, timedelta
from jose import jwt

SECRET_KEY = "mysecret"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def create_access_token(data: dict, role: str):
    to_encode = data.copy()
    to_encode.update({"role": role, "exp": datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

@app.post("/token")
async def login(role: str):
    access_token = create_access_token(data={"sub": "user_id"}, role=role)
    return {"access_token": access_token, "token_type": "bearer"}

In this example, the JWT token includes the user's role in the payload. When a user logs in, the role is passed to the create_access_token function, which adds the role to the token before encoding it.


How do you protect routes based on multiple roles in FastAPI?

If a route should be accessible by users with multiple roles, you can modify the verify_token function to check if the user's role is in a list of allowed roles.

Example of protecting routes for multiple roles:

def verify_token(token: str, allowed_roles: list):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        role = payload.get("role")
        if role is None or role not in allowed_roles:
            raise HTTPException(status_code=403, detail="Insufficient permissions")
    except jwt.JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.get("/editor/")
async def read_editor_data(token: str = Depends(oauth2_scheme)):
    verify_token(token, ["admin", "editor"])
    return {"message": "Welcome, editor or admin!"}

In this example, the verify_token function checks if the user's role is one of the allowed roles (admin or editor). Users with either role are granted access to the /editor/ route.


How do you implement hierarchical roles in RBAC in FastAPI?

In hierarchical RBAC, roles have levels of authority. For example, an "admin" role might have all permissions, while a "user" role might have fewer permissions. To implement hierarchical roles, you define a role hierarchy and check if a user's role has sufficient authority to access the route.

Example of implementing hierarchical roles:

ROLE_HIERARCHY = {
    "admin": 3,
    "editor": 2,
    "user": 1
}

def verify_token(token: str, required_role: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_role = payload.get("role")
        if user_role is None or ROLE_HIERARCHY[user_role] < ROLE_HIERARCHY[required_role]:
            raise HTTPException(status_code=403, detail="Insufficient permissions")
    except jwt.JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.get("/manage/")
async def manage_data(token: str = Depends(oauth2_scheme)):
    verify_token(token, "editor")  # Editors and admins can access
    return {"message": "Access granted"}

In this example, the ROLE_HIERARCHY dictionary defines a hierarchy where "admin" has the highest authority and "user" has the lowest. The verify_token function checks if the user's role has sufficient authority based on the hierarchy to access the route.


How do you assign multiple roles to a user in RBAC for FastAPI?

To assign multiple roles to a user, you can store the roles as a list in the JWT token and check if any of the user's roles match the required roles for the route.

Example of assigning multiple roles to a user:

def create_access_token(data: dict, roles: list):
    to_encode = data.copy()
    to_encode.update({"roles": roles, "exp": datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

def verify_token(token: str, allowed_roles: list):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_roles = payload.get("roles", [])
        if not any(role in allowed_roles for role in user_roles):
            raise HTTPException(status_code=403, detail="Insufficient permissions")
    except jwt.JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.get("/data/")
async def read_data(token: str = Depends(oauth2_scheme)):
    verify_token(token, ["admin", "editor"])
    return {"message": "Access granted"}

In this example, the JWT token contains a list of roles, and the verify_token function checks if any of the user's roles are in the list of allowed roles for the route.


How do you combine JWT-based authentication and RBAC in FastAPI?

In FastAPI, JWT-based authentication and RBAC can be combined by verifying both the token's validity and the user's role before allowing access to a route. JWTs handle authentication, while RBAC checks ensure that the user has the correct role to access specific resources.

Example of combining JWT authentication and RBAC:

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import jwt

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

SECRET_KEY = "mysecret"
ALGORITHM = "HS256"

def verify_token(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except jwt.JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

def check_role(payload: dict, allowed_roles: list):
    role = payload.get("role")
    if role is None or role not in allowed_roles:
        raise HTTPException(status_code=403, detail="Insufficient permissions")

@app.get("/protected/")
async def protected_route(token: str = Depends(oauth2_scheme)):
    payload = verify_token(token)
    check_role(payload, ["admin", "user"])
    return {"message": "Access granted"}

In this example, the token is first verified for validity using verify_token, and then the user's role is checked with check_role to ensure they have the required role to access the route.

Ads