JWT Example
A complete example using casbin-fastapi-decorator with the jwt extra. Replaces manual token parsing with JWTUserProvider.
Install
pip install "casbin-fastapi-decorator[jwt,file]" uvicorn PyJWT python-multipart
Project structure
my-app/
├── casbin/
│ ├── model.conf
│ └── policy.csv
├── auth.py
├── authz.py
├── model.py
└── main.py
Casbin files
Same as the basic example.
model.py
Same as the basic example.
auth.py
from casbin_fastapi_decorator_jwt import JWTUserProvider
from model import UserSchema
SECRET_KEY = "super-secret-key"
ALGORITHM = "HS256"
user_provider = JWTUserProvider(
secret_key=SECRET_KEY,
algorithm=ALGORITHM,
user_model=UserSchema,
)
JWTUserProvider handles everything: extracting the Bearer token, decoding the JWT, and validating the payload into UserSchema.
authz.py
from auth import user_provider
from contextlib import asynccontextmanager
from collections.abc import AsyncIterator
from fastapi import FastAPI, HTTPException
from casbin_fastapi_decorator import PermissionGuard
from casbin_fastapi_decorator_file import CachedFileEnforcerProvider
enforcer_provider = CachedFileEnforcerProvider(
model_path="casbin/model.conf",
policy_path="casbin/policy.csv",
)
@asynccontextmanager
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
async with enforcer_provider:
yield
guard = PermissionGuard(
user_provider=user_provider,
enforcer_provider=enforcer_provider,
error_factory=lambda *_: HTTPException(403, "Forbidden"),
)
main.py
import jwt as pyjwt
from typing import Annotated
from auth import ALGORITHM, SECRET_KEY, user_provider
from authz import guard, lifespan
from fastapi import Depends, FastAPI, Form
from model import Permission, PostCreateSchema, PostSchema, Resource, Role, UserSchema
app = FastAPI(title="Core + JWT Example", lifespan=lifespan)
MOCK_DB = [
PostSchema(id=1, title="First Post"),
PostSchema(id=2, title="Second Post"),
]
@app.post("/login")
async def login(role: Role) -> str:
"""Generate a signed JWT token for the given role."""
return pyjwt.encode({"role": role}, SECRET_KEY, algorithm=ALGORITHM)
@app.get("/me")
@guard.auth_required()
async def me(user: Annotated[UserSchema, Depends(user_provider)]) -> UserSchema:
return user
@app.get("/articles")
@guard.require_permission(Resource.POST, Permission.READ)
async def list_posts() -> list[PostSchema]:
return MOCK_DB
@app.post("/articles")
@guard.require_permission(Resource.POST, Permission.WRITE)
async def create_post(data: Annotated[PostCreateSchema, Form]) -> PostSchema:
pk = sorted(MOCK_DB, key=lambda p: p.id)[-1].id + 1
post = PostSchema(id=pk, title=data.title)
MOCK_DB.append(post)
return post
Running
uvicorn main:app --reload
Testing
# Get a signed JWT for the editor role
TOKEN=$(curl -s -X POST "http://localhost:8000/login?role=editor" | tr -d '"')
# Get current user info
curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/me
# → {"role": "editor"}
# List posts (allowed)
curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/articles
# → [...]
# Create post (allowed for editor)
curl -X POST -H "Authorization: Bearer $TOKEN" \
-F "title=New Post" http://localhost:8000/articles
# → {"id": 3, "title": "New Post"}
# Try with viewer role
VIEWER_TOKEN=$(curl -s -X POST "http://localhost:8000/login?role=viewer" | tr -d '"')
curl -X POST -H "Authorization: Bearer $VIEWER_TOKEN" \
-F "title=New Post" http://localhost:8000/articles
# → {"detail": "Forbidden"}
Key difference from basic example
The authentication flow changes in auth.py - JWTUserProvider handles the
full JWT lifecycle. The file-based enforcer stays cached and hot-reloadable,
just like in the basic example.