Protected Routes¶
fastapi-fullauth provides FastAPI dependencies to protect your routes. Use them with Depends() or the Annotated type aliases.
Dependency types¶
CurrentUser¶
Any authenticated user (active account required).
from fastapi_fullauth.dependencies import CurrentUser
@app.get("/profile")
async def profile(user: CurrentUser):
return {"email": user.email, "roles": user.roles}
VerifiedUser¶
Authenticated user with a verified email address.
from fastapi_fullauth.dependencies import VerifiedUser
@app.get("/dashboard")
async def dashboard(user: VerifiedUser):
return {"email": user.email}
Returns 403 Forbidden if the user's email is not verified.
SuperUser¶
Authenticated user with is_superuser=True.
from fastapi_fullauth.dependencies import SuperUser
@app.delete("/admin/users/{user_id}")
async def delete_user(user_id: str, admin: SuperUser):
...
Returns 403 Forbidden if the user is not a superuser.
require_role¶
Check that the user has at least one of the specified roles. Superusers bypass all role checks.
from fastapi import Depends
from fastapi_fullauth.dependencies import require_role
@app.get("/editor")
async def editor_panel(user=Depends(require_role("editor"))):
return {"msg": "welcome, editor"}
# multiple roles — user needs at least one
@app.get("/content")
async def content(user=Depends(require_role("editor", "author"))):
return {"msg": "welcome"}
require_permission¶
Check that the user has at least one of the specified permissions. Permissions are resolved through roles — a user with role "editor" gets all permissions assigned to that role.
from fastapi import Depends
from fastapi_fullauth.dependencies import require_permission
@app.delete("/posts/{id}")
async def delete_post(id: str, user=Depends(require_permission("posts:delete"))):
...
# multiple permissions — user needs at least one
@app.put("/posts/{id}")
async def edit_post(id: str, user=Depends(require_permission("posts:edit", "posts:admin"))):
...
Superusers bypass all permission checks.
Setting up permissions¶
Permissions are assigned to roles, not directly to users:
# Assign permissions to a role (superuser only)
curl -X POST http://localhost:8000/api/v1/auth/admin/assign-permission \
-H "Authorization: Bearer <superuser-token>" \
-H "Content-Type: application/json" \
-d '{"role": "editor", "permission": "posts:create"}'
curl -X POST http://localhost:8000/api/v1/auth/admin/assign-permission \
-H "Authorization: Bearer <superuser-token>" \
-H "Content-Type: application/json" \
-d '{"role": "editor", "permission": "posts:edit"}'
# List permissions for a role
curl http://localhost:8000/api/v1/auth/admin/role-permissions/editor \
-H "Authorization: Bearer <superuser-token>"
# → ["posts:create", "posts:edit"]
Or programmatically:
await adapter.assign_permission_to_role("editor", "posts:create")
await adapter.assign_permission_to_role("editor", "posts:edit")
await adapter.remove_permission_from_role("editor", "posts:create")
# resolve all permissions for a user (through their roles)
perms = await adapter.get_user_permissions(user.id)
# → ["posts:edit"]
require_role vs require_permission¶
require_role |
require_permission |
|
|---|---|---|
| Checks | Role names on the user | Permissions resolved through roles |
| Setup | Just assign roles | Assign roles + map permissions to roles |
| Use case | Simple apps ("admin vs user") | Fine-grained access ("can edit posts?") |
| Change access | Modify code | Update DB mappings |
How it works¶
All dependencies follow the same flow:
- Extract the JWT from the
Authorization: Bearer <token>header (or cookie backend) - Decode and validate the token (expiry, blacklist, signature)
- Look up the user by
sub(user ID) from the token payload - Apply additional checks (verified, superuser, roles)
If any step fails, a 401 Unauthorized or 403 Forbidden response is returned automatically.
Typed dependencies for custom schemas¶
If you use custom user schemas, the default CurrentUser type resolves to the base UserSchema. Use the factory functions for full type safety:
from typing import Annotated
from fastapi import Depends
from fastapi_fullauth.dependencies import (
get_current_user_dependency,
get_verified_user_dependency,
get_superuser_dependency,
)
# your custom schema
from myapp.schemas import MyUserSchema
MyCurrentUser = Annotated[MyUserSchema, Depends(get_current_user_dependency(MyUserSchema))]
MyVerifiedUser = Annotated[MyUserSchema, Depends(get_verified_user_dependency(MyUserSchema))]
MySuperUser = Annotated[MyUserSchema, Depends(get_superuser_dependency(MyUserSchema))]
@app.get("/profile")
async def profile(user: MyCurrentUser):
return {"name": user.display_name} # IDE knows this field exists
Using with the function form¶
If you prefer the function form over Annotated types:
from fastapi import Depends
from fastapi_fullauth.dependencies import current_user, current_active_verified_user, current_superuser
@app.get("/profile")
async def profile(user=Depends(current_user)):
return user
Role management¶
Roles are managed through the admin endpoints (superuser only):
# Assign a role
curl -X POST http://localhost:8000/api/v1/auth/admin/assign-role \
-H "Authorization: Bearer <superuser-token>" \
-H "Content-Type: application/json" \
-d '{"user_id": "...", "role": "editor"}'
# Remove a role
curl -X POST http://localhost:8000/api/v1/auth/admin/remove-role \
-H "Authorization: Bearer <superuser-token>" \
-H "Content-Type: application/json" \
-d '{"user_id": "...", "role": "editor"}'
You can also manage roles programmatically through the adapter: