Skip to main content

Decorator-based authorization for FastAPI with Casbin, without middleware

Project description

casbin-fastapi-decorator logo

casbin-fastapi-decorator

Authorization decorator factory for FastAPI based on Casbin and fastapi-decorators.

PyPI Python PyPI Downloads License CI codecov

📚 Documentation · PyPI · Casbin Ecosystem


Decorators are applied directly to routes — no middleware, no extra parameters in your function signatures.

Why decorator, not middleware?

Feature casbin-fastapi-decorator fastapi-authz / fastapi-casbin-auth
Approach Decorator per route Global middleware
Per-route permission config
Dynamic objects from request AccessSubject
No extra params in endpoint signature
Native FastAPI DI integration ⚠️ partial
JWT extras
DB-backed policies (SQLAlchemy async)
File policies with hot-reload
Casdoor OAuth2 integration
Works with APIRouter

Middleware-based authorization checks every incoming request globally. With a decorator, you configure permissions exactly where the route is defined — no hidden side effects, no boilerplate dependencies in every function signature.

Installation

pip install casbin-fastapi-decorator

Optional extras — install only what you need:

pip install "casbin-fastapi-decorator[file]"     # File policies with hot-reload (recommended)
pip install "casbin-fastapi-decorator[jwt]"      # JWT authentication
pip install "casbin-fastapi-decorator[db]"       # Policies from DB (SQLAlchemy) with hot-reload
pip install "casbin-fastapi-decorator[casdoor]"  # Casdoor OAuth2

Quick start

from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from casbin_fastapi_decorator import AccessSubject, PermissionGuard
from casbin_fastapi_decorator_file import CachedFileEnforcerProvider

# 1. Providers — regular FastAPI dependencies
async def get_current_user() -> dict:
    return {"sub": "alice", "role": "admin"}

# CachedFileEnforcerProvider loads the enforcer once and hot-reloads
# automatically when model.conf or policy.csv changes on disk.
enforcer_provider = CachedFileEnforcerProvider(
    model_path="model.conf",
    policy_path="policy.csv",
)

# 2. Decorator factory
guard = PermissionGuard(
    user_provider=get_current_user,
    enforcer_provider=enforcer_provider,
    error_factory=lambda user, *rv: HTTPException(403, "Forbidden"),
)

# 3. Wire lifespan to start/stop the file watcher
@asynccontextmanager
async def lifespan(app: FastAPI):
    async with enforcer_provider:
        yield

app = FastAPI(lifespan=lifespan)

# 4. Authentication only
@app.get("/me")
@guard.auth_required()
async def me():
    return {"ok": True}

# 5. Static permission check
@app.get("/articles")
@guard.require_permission("articles", "read")
async def list_articles():
    return []

# 6. Dynamic check — object resolved from request
async def get_article(article_id: int) -> dict:
    return {"id": article_id, "owner": "alice"}

@app.get("/articles/{article_id}")
@guard.require_permission(
    AccessSubject(val=get_article, selector=lambda a: a["owner"]),
    "read",
)
async def read_article(article_id: int):
    return {"article_id": article_id}

Arguments of require_permission are passed to enforcer.enforce(user, *args) in the same order. AccessSubject is resolved via FastAPI DI, then transformed by the selector.

API

PermissionGuard

PermissionGuard(
    user_provider=...,       # FastAPI dependency that returns the current user
    enforcer_provider=...,   # FastAPI dependency that returns a casbin.Enforcer
    error_factory=...,       # callable(user, *rvals) -> Exception
)
Method Description
auth_required() Decorator: authentication only (user_provider must not raise)
require_permission(*args, error_factory=None) Decorator: permission check via enforcer.enforce(user, *args). Optional error_factory overrides the guard-level factory for this route only.

AccessSubject

AccessSubject(
    val=get_item,                        # FastAPI dependency
    selector=lambda item: item["name"],  # transformation before enforce
)

Wraps a dependency whose value is resolved from the request and passed to the enforcer. By default, selector is identity (lambda x: x).

Per-route error responses

Override the guard-level error_factory on specific routes to customize error handling:

def article_not_found_error(user, *resolved_args) -> HTTPException:
    """Return 404 instead of 403 for denied access."""
    return HTTPException(status_code=404, detail="Article not found")

@app.get("/articles/draft")
@guard.require_permission(
    "article", "write",
    error_factory=article_not_found_error,
)
async def read_draft():
    return {"title": "Draft Article"}

When a user without write permission accesses this route, they'll receive a 404 Not Found instead of the default 403 Forbidden, effectively hiding the resource's existence.

File provider

casbin-fastapi-decorator-file — loads the Casbin enforcer once from model.conf + policy.csv and hot-reloads automatically when either file changes on disk (via watchdog).

pip install "casbin-fastapi-decorator[file]"
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) -> AsyncGenerator[None, None]:
    async with enforcer_provider:   # starts watchdog
        yield                       # stops watchdog on shutdown

guard = PermissionGuard(
    user_provider=get_current_user,
    enforcer_provider=enforcer_provider,
    error_factory=lambda *_: HTTPException(403, "Forbidden"),
)

Edit policy.csv while the app is running — the enforcer reloads on the next request with zero downtime. The same applies to model.conf changes.

Recommended for all file-based setups. Compared to a plain async def get_enforcer() that returns casbin.Enforcer(...), this provider avoids re-reading files on every request.

See packages/casbin-fastapi-decorator-file/README.md for full API and usage.

JWT provider

casbin-fastapi-decorator-jwt — extracts and validates a JWT from the Bearer header and/or a cookie.

pip install "casbin-fastapi-decorator[jwt]"

See packages/casbin-fastapi-decorator-jwt/README.md for full API and usage.

DB provider

casbin-fastapi-decorator-db — loads Casbin policies from a SQLAlchemy async session with caching and hot-reload.

pip install "casbin-fastapi-decorator[db]"

The enforcer is cached and reloaded automatically when:

  • model.conf changes on disk (watchdog)
  • DB policy rows change — detected by SHA-256 hash, polled every poll_interval seconds (default 30 s)
from casbin_fastapi_decorator_db import DatabaseEnforcerProvider

enforcer_provider = DatabaseEnforcerProvider(
    model_path="casbin/model.conf",
    session_factory=async_session,
    policy_model=Policy,
    policy_mapper=lambda p: (p.sub, p.obj, p.act),
    poll_interval=30.0,  # seconds between DB hash checks
)

@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    async with enforcer_provider:   # starts watchdog + polling task
        yield

See packages/casbin-fastapi-decorator-db/README.md for full API and usage.

Casdoor provider

casbin-fastapi-decorator-casdoor — Casdoor OAuth2 authentication and remote Casbin policy enforcement.

pip install "casbin-fastapi-decorator[casdoor]"
from casbin_fastapi_decorator_casdoor import CasdoorEnforceTarget, CasdoorIntegration

casdoor = CasdoorIntegration(
    endpoint="http://localhost:8000",
    client_id="...", client_secret="...", certificate=cert,
    org_name="my_org", application_name="my_app",
    target=CasdoorEnforceTarget(
        enforce_id=lambda parsed: f"{parsed['owner']}/my_enforcer",
    ),
)
app.include_router(casdoor.router)   # GET /login, GET /callback, POST /logout
guard = casdoor.create_guard()

CasdoorEnforceTarget selects the Casdoor enforce mode — by enforcer, permission, model, resource, or owner. Values can be static strings or callables resolved from the JWT payload at request time.

POST /logout calls Casdoor's SSO logout endpoint (/api/sso-logout?logoutAll=true) when an access-token cookie is present, then clears local auth cookies.

See packages/casbin-fastapi-decorator-casdoor/README.md for full API, compose pattern, and usage.

Examples

Example Description
examples/core Bearer token auth, plain file-based policies
examples/core-file Bearer token auth, file policies with hot-reload via CachedFileEnforcerProvider
examples/core-jwt JWT auth via JWTUserProvider, file-based policies
examples/core-db Bearer token auth, DB policies with hot-reload via DatabaseEnforcerProvider
examples/core-casdoor Casdoor OAuth2 auth + remote enforcement, facade and compose patterns

Development

Requires Python 3.10+, uv, task.

task install           # uv sync --all-groups + install all packages
task lint              # ruff + ty + bandit for all packages
task tests             # all tests (core + jwt + db + casdoor + file)

Individual package tasks:

task core:lint         task core:test
task jwt:lint          task jwt:test
task db:lint           task db:test         # requires Docker (testcontainers)
task casdoor:lint      task casdoor:test
task file:lint         task file:test

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

casbin_fastapi_decorator-1.2.1.tar.gz (7.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

casbin_fastapi_decorator-1.2.1-py3-none-any.whl (9.1 kB view details)

Uploaded Python 3

File details

Details for the file casbin_fastapi_decorator-1.2.1.tar.gz.

File metadata

  • Download URL: casbin_fastapi_decorator-1.2.1.tar.gz
  • Upload date:
  • Size: 7.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for casbin_fastapi_decorator-1.2.1.tar.gz
Algorithm Hash digest
SHA256 e22d68bcb868d190004eadf6a101d1d7775d0a4ca4416ea4e2b3665ee1516aae
MD5 6da5945020f9bdd54ac6eb1104f02957
BLAKE2b-256 0b869369f4b2c23e1c6038f69c8abd9f9a54cbcc94577809a2cb896ccef47049

See more details on using hashes here.

File details

Details for the file casbin_fastapi_decorator-1.2.1-py3-none-any.whl.

File metadata

  • Download URL: casbin_fastapi_decorator-1.2.1-py3-none-any.whl
  • Upload date:
  • Size: 9.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for casbin_fastapi_decorator-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 faae7e44add857311b36ae952d3ecdb3d98b6fa39d4c6ea888f9b2330fefadd2
MD5 4b6c7cbf7c97b176bcae17e6a2e2d5e2
BLAKE2b-256 8ec87b2341fde6f499e86a8e421c1bd84c308cbec4b5c87f5267ddfe848abb22

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page