require_permission
guard.require_permission(*args) returns a decorator that performs a full authorization check via the Casbin enforcer.
Usage
@app.get("/posts")
@guard.require_permission("post", "read")
async def list_posts():
...
How it works
When the decorated route is called:
user_provideris resolved — returns the current userenforcer_provideris resolved — returns acasbin.Enforcer- Each argument in
*argsis resolved:- Plain values (strings, enums, etc.) are passed as-is
AccessSubjectinstances are resolved via FastAPI DI and transformed
- The enforcer is called:
enforcer.enforce(user, *resolved_args) - If the result is
False,error_factory(user, *resolved_args)is raised - If
True, the route handler runs
Static arguments
The simplest case — fixed strings or enums:
@app.delete("/posts/{post_id}")
@guard.require_permission("post", "delete")
async def delete_post(post_id: int):
...
You can use any values that Casbin's model understands — strings, enums, etc.:
from enum import StrEnum
class Resource(StrEnum):
POST = "post"
class Action(StrEnum):
READ = "read"
WRITE = "write"
@app.get("/posts")
@guard.require_permission(Resource.POST, Action.READ)
async def list_posts():
...
Dynamic arguments
When a permission argument depends on request data (path params, query params, request body), use AccessSubject:
from casbin_fastapi_decorator import AccessSubject
async def get_post_owner(post_id: int) -> str:
post = await db.get(post_id)
return post.owner_id
@app.put("/posts/{post_id}")
@guard.require_permission(
"post",
AccessSubject(val=get_post_owner, selector=lambda owner: owner),
)
async def update_post(post_id: int):
...
Argument order
Arguments are passed to enforcer.enforce(user, *args) in exactly the order you specify them. Make sure the order matches your Casbin model's [request_definition]:
# model.conf
[request_definition]
r = sub, obj, act
# sub = user (always first, added by the guard)
# obj = first arg
# act = second arg
@guard.require_permission("post", "read")
# ^^^^^^ ^^^^^^
# obj act