Class-Based Permissions¶
Class-based permissions are the primary way to define permission checks. You subclass Permission and
implement the check_permissions method.
Basic Permission¶
The simplest permission class has no fields and uses only injected parameters:
from fastapi import Depends, FastAPI, Request
from fastapi_has_permissions import Permission
class HasAuthorizationHeader(Permission):
async def check_permissions(self, request: Request) -> bool:
return "Authorization" in request.headers
app = FastAPI()
@app.get(
"/protected",
dependencies=[Depends(HasAuthorizationHeader())],
)
async def protected():
return {"message": "You have access!"}
Permissions with Parameters¶
Permission classes are automatically turned into dataclasses. You can add fields to parameterize the permission check:
from dataclasses import dataclass
from fastapi import Depends, FastAPI, Request
from fastapi_has_permissions import Permission
@dataclass
class HasRole(Permission):
role: str
async def check_permissions(self, request: Request) -> bool:
return request.headers.get("role") == self.role
app = FastAPI()
@app.get(
"/admin",
dependencies=[Depends(HasRole(role="admin"))],
)
async def admin_only():
return {"message": "Admin access granted"}
@app.get(
"/moderator",
dependencies=[Depends(HasRole(role="moderator"))],
)
async def moderator_only():
return {"message": "Moderator access granted"}
Note
The @dataclass decorator is optional -- Permission subclasses are automatically
converted to dataclasses. However, adding it explicitly can help your IDE with type hints
and autocompletion.
FastAPI Dependency Injection in check_permissions¶
The check_permissions method fully supports FastAPI's dependency injection. Any parameter
you declare will be resolved by FastAPI at request time:
from typing import Annotated
from fastapi import Depends, FastAPI, Header
from fastapi_has_permissions import Permission
class HasAdminRole(Permission):
async def check_permissions(self, role: Annotated[str, Header()]) -> bool:
return role == "admin"
app = FastAPI()
@app.get(
"/admin",
dependencies=[Depends(HasAdminRole())],
)
async def admin_only():
return {"message": "Admin access granted"}
You can also use Depends() inside check_permissions to inject other dependencies:
from typing import Annotated
from fastapi import Depends, FastAPI, Header
from fastapi_has_permissions import Permission
async def get_current_user_role(x_role: Annotated[str, Header()]) -> str:
return x_role
class HasAdminRole(Permission):
async def check_permissions(
self,
role: Annotated[str, Depends(get_current_user_role)],
) -> bool:
return role == "admin"
app = FastAPI()
@app.get(
"/admin",
dependencies=[Depends(HasAdminRole())],
)
async def admin_only():
return {"message": "Admin access granted"}
Return Types¶
The check_permissions method returns a CheckResult, which can be:
True-- permission grantedFalse-- permission denied (raisesHTTPExceptionwith403)Failed(reason="...")-- permission denied with a custom messageSkipped(reason="...")-- permission check is skipped entirely (treated as granted)
from fastapi_has_permissions import Permission, fail
from fastapi_has_permissions._results import CheckResult, Failed
class HasValidToken(Permission):
async def check_permissions(self, request: Request) -> CheckResult:
token = request.headers.get("Authorization")
if token is None:
return Failed(reason="Authorization header is missing")
if token != "Bearer valid-token":
return Failed(reason="Invalid token")
return True
Tip
See Skip & Fail Helpers for more convenient ways to control the permission check flow.
Router-Level Permissions¶
Instead of adding permissions to individual routes, you can apply them to an entire router:
from fastapi import APIRouter, Depends, FastAPI
from fastapi_has_permissions import Permission
class IsAuthenticated(Permission):
async def check_permissions(self, request: Request) -> bool:
return "Authorization" in request.headers
app = FastAPI()
router = APIRouter(
prefix="/api",
dependencies=[Depends(IsAuthenticated())],
)
@router.get("/users")
async def get_users():
return [{"name": "Alice"}, {"name": "Bob"}]
@router.get("/items")
async def get_items():
return [{"name": "Item 1"}, {"name": "Item 2"}]
app.include_router(router)
All routes under this router will require the Authorization header.