Skip to content

Protecting routes

FastAuth exposes three FastAPI dependencies in fastauth.api.deps. They read the access token from the Authorization: Bearer header or the access_token cookie, and return the authenticated UserData.

require_auth

from fastapi import Depends
from fastauth.api.deps import require_auth
from fastauth.types import UserData

@app.get("/profile")
async def profile(user: UserData = Depends(require_auth)):
    return {"email": user["email"], "id": user["id"]}

Behavior (api/deps.py):

  • Token from header or cookie. Decoded via core.tokens.decode_token.
  • type claim must be "access"; otherwise treated as unauthenticated (no error raised inside get_current_userrequire_auth then raises 401).
  • The user is loaded via adapter.get_user_by_id. If the user is missing or is_active=False, require_auth raises 401.
  • If the token came from a cookie, and the request method is POST/PUT/PATCH/DELETE, the CSRF double-submit check runs: the X-CSRF-Token header must equal the csrf_token cookie (constant-time compared). Mismatch → 403.

Using it with dependencies=

You can attach the dependency to a whole router or method without receiving the user:

from fastapi import APIRouter, Depends
router = APIRouter(dependencies=[Depends(require_auth)])

@router.get("/things")
async def list_things(): ...

require_role

Requires RBAC: auth.role_adapter must be set on the FastAuth instance.

from fastauth.api.deps import require_role

@app.get("/admin")
async def admin_area(user: UserData = Depends(require_role("admin"))):
    return {"user_id": user["id"]}
  • 401 if not authenticated.
  • 403 Insufficient role if the user does not hold the role.
  • 500 RBAC is not configured if auth.role_adapter is None.

require_permission

from fastauth.api.deps import require_permission

@app.get("/reports")
async def reports(user: UserData = Depends(require_permission("reports:read"))):
    return {...}

Passes if the user holds any role containing the permission string. Same failure codes as require_role.

Wiring RBAC

config = FastAuthConfig(
    ...,
    roles=[
        {"name": "admin", "permissions": ["users:read", "users:write", "users:delete"]},
        {"name": "user",  "permissions": ["profile:read", "profile:write"]},
    ],
    default_role="user",
)
auth = FastAuth(config)
auth.role_adapter = adapter.role          # required

@asynccontextmanager
async def lifespan(app: FastAPI):
    await adapter.create_tables()
    await auth.initialize_roles()         # seeds roles from config.roles
    yield

See RBAC for the /auth/roles admin endpoints.

Combining dependencies

require_role and require_permission already depend on require_auth. Do not nest them manually — passing both to Depends re-decodes the token twice. Use one of the three.

Common mistakes

  • 500 RBAC is not configured — you forgot auth.role_adapter = adapter.role. Setting roles= in the config alone is not enough.
  • Roles exist but user has none — you did not call await auth.initialize_roles() in the lifespan, or the user was created before default_role was set. Existing users do not retroactively receive default_role.
  • 403 CSRF token missing or invalid — you are using cookie delivery and did not send X-CSRF-Token matching the csrf_token cookie on a POST/PUT/PATCH/DELETE.
  • Sending the refresh token as a Bearer — only access tokens are accepted by require_auth. Refresh with POST /auth/refresh first.