Quick Start
A complete working example in under 5 minutes.
1. Install
pip install "casbin-fastapi-decorator[file]" uvicorn
2. Create the Casbin model
Create casbin/model.conf:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.obj == p.obj && r.sub.role == p.sub && r.act == p.act
3. Create the policy file
Create casbin/policy.csv:
p, viewer, post, read
p, editor, post, read
p, editor, post, write
p, admin, post, read
p, admin, post, write
p, admin, post, delete
4. Create the app
from contextlib import asynccontextmanager
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from pydantic import BaseModel
from casbin_fastapi_decorator import PermissionGuard
from casbin_fastapi_decorator_file import CachedFileEnforcerProvider
bearer = HTTPBearer(auto_error=False)
# --- User model ---
class User(BaseModel):
role: str
# --- Providers ---
async def get_current_user(
credentials: Annotated[
HTTPAuthorizationCredentials | None,
Security(bearer),
],
) -> User:
if not credentials:
raise HTTPException(401, "Unauthorized")
return User(role=credentials.credentials)
enforcer_provider = CachedFileEnforcerProvider(
model_path="casbin/model.conf",
policy_path="casbin/policy.csv",
)
@asynccontextmanager
async def lifespan(_app: FastAPI):
async with enforcer_provider:
yield
# --- Guard ---
guard = PermissionGuard(
user_provider=get_current_user,
enforcer_provider=enforcer_provider,
error_factory=lambda *_: HTTPException(403, "Forbidden"),
)
app = FastAPI(lifespan=lifespan)
# --- Routes ---
@app.get("/me")
@guard.auth_required()
async def me(user: Annotated[User, Depends(get_current_user)]) -> User:
return user
@app.get("/posts")
@guard.require_permission("post", "read")
async def list_posts() -> list[dict]:
return [{"id": 1, "title": "Hello World"}]
@app.post("/posts")
@guard.require_permission("post", "write")
async def create_post() -> dict:
return {"id": 2, "title": "New Post"}
5. Test it
uvicorn main:app --reload
Send a request as viewer (can read but not write):
# This works
curl -H "Authorization: Bearer viewer" http://localhost:8000/posts
# This returns 403
curl -X POST -H "Authorization: Bearer viewer" http://localhost:8000/posts
Send as editor (can read and write):
# Both work
curl -H "Authorization: Bearer editor" http://localhost:8000/posts
curl -X POST -H "Authorization: Bearer editor" http://localhost:8000/posts
What's happening
- The request arrives at
GET /posts guard.require_permission("post", "read")intercepts itget_current_userresolves the user from the Bearer token — returnsUser(role="viewer")CachedFileEnforcerProviderreturns the cached enforcer loaded frommodel.confandpolicy.csv- The matcher checks
user.role == p.sub, soenforce(user, "post", "read")returnsTrue - The route handler runs and returns the posts
If you edit casbin/policy.csv while the app is running, the next request will
pick up the change automatically.
Next steps
- PermissionGuard — learn all configuration options
- AccessSubject — dynamic permissions based on request data
- File extra — cached file-based enforcer with hot-reload
- JWT extra — replace manual token parsing with
JWTUserProvider