Role-gated routes¶
One validator, one keyword argument, several levels of access.
import os
from typing import Annotated
from fastapi import FastAPI, Security
from fastapi_multiauth import APIKeyCookieAuth, ForbiddenError, UnauthorizedError
SECRET_KEY = os.environ["SECRET_KEY"]
ROLES = {"alice": "admin", "bob": "member"}
async def validate_session(username: str, *, role: str | None = None) -> str:
if username not in ROLES:
raise UnauthorizedError()
if role is not None and ROLES[username] != role:
raise ForbiddenError() # signed in, but not allowed: 403
return username
session = APIKeyCookieAuth(
name="__Host-session", validator=validate_session, secret_key=SECRET_KEY
)
CurrentUser = Annotated[str, Security(session)]
AdminUser = Annotated[str, Security(session.require(role="admin"))]
app = FastAPI()
@app.get("/me")
async def me(username: CurrentUser):
return {"username": username}
@app.get("/admin/stats")
async def stats(username: AdminUser):
return {"users": len(ROLES)}
session.require(role="admin") is a copy of session that passes role="admin" to the validator. A signed-in user without the role gets a 403.