Skip to main content

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.