Skip to main content

Ergonomic, lightweight JWT authentication for FastAPI. Secure your routes instantly with plug-and-play dependency injection

Project description

FAuth

An ergonomic, plug-and-play authentication library for FastAPI.

fauth eliminates boilerplate around JWT, password hashing, user fetching, and Role-Based Access Control (RBAC) by leveraging FastAPI's Dependency Injection (Depends), Pydantic models, and Python Protocols.

Features

  • Protocol-Based User Fetching: Complete inversion of control. You implement a simple UserLoader protocol to define how to fetch a user from a token payload.
  • Plug-and-Play Configuration: Centralized settings via Pydantic (AuthConfig). Configure once, inject everywhere.
  • Pluggable Transports: Extensible Transport protocol with a built-in BearerTransport for Authorization header tokens.
  • Built-in Password Hashing: Uses modern Argon2 via pwdlib.
  • RBAC: Flexible require_roles and require_permissions dependencies for endpoint authorization.
  • Secure Router: SecureAPIRouter applies authentication as a router-level dependency, securing all its routes automatically.
  • Testing Utilities: Ships fake implementations (FakeUserLoader) and a build_fake_auth_provider() factory so consumers can write unit tests with zero boilerplate.
  • Type Safety: Fully annotated for MyPy and IDE integration.

Quick Start

pip install fauth
uv add fauth

How to use FAuth

To adopt FAuth, define an async user lookup function (implementing the UserLoader protocol), supply an AuthConfig, and instantiate the AuthProvider. You can then inject the provider directly into FastAPI endpoints.

from fastapi import FastAPI, Depends
from pydantic import BaseModel
from fauth import AuthConfig, AuthProvider, TokenPayload, SecureAPIRouter

app = FastAPI()

# 1. Define your internal user model
class User(BaseModel):
    id: str
    username: str
    is_active: bool = True
    roles: list[str] = []

# Mock database
DB: dict[str, User] = {
    "user-123": User(id="user-123", username="alice", roles=["admin"])
}

# 2. Define the callback that retrieves a user from the decoded JWT
async def load_user(payload: TokenPayload) -> User | None:
    return DB.get(payload.sub)

# 3. Instantiate the auth component (ideally wired in DI or at module-level)
config = AuthConfig(secret_key="my-super-secret-key", algorithm="HS256")
auth: AuthProvider[User] = AuthProvider(config=config, user_loader=load_user)

# --- Routes ---

@app.post("/login")
async def login():
    # Example logic: password hashing checks omitted for brevity
    # 4. Use `auth.login` to issue tokens
    return await auth.login(sub="user-123")

@app.get("/me")
async def get_me(user: User = Depends(auth.require_user())):
    # 5. `auth.require_user()` secures the endpoint automatically
    return {"message": f"Hello {user.username}"}

@app.get("/admin")
async def get_admin_data(user: User = Depends(auth.require_roles("admin"))):
    # 6. `auth.require_roles()` enforces RBAC implicitly
    return {"secret_data": "Top secret admin info"}

# --- Securing Multiple Routes ---

# 7. Use `SecureAPIRouter` to protect an entire group of routes.
# Any route added to this router will require an active user automatically.
secure_router = SecureAPIRouter(auth_provider=auth, prefix="/internal", tags=["Protected"])

@secure_router.get("/dashboard")
async def get_dashboard():
    # This endpoint is secured by FAuth without needing Depends in the signature!
    return {"data": "Secure dashboard"}

app.include_router(secure_router)

Custom Token Payload

By default, FAuth decodes JWTs into its built-in TokenPayload schema. If you need custom claims in your tokens (e.g., tenant_id, organization_id), subclass TokenPayload and pass it to AuthProvider:

from fauth import AuthConfig, AuthProvider, TokenPayload

class MyTokenPayload(TokenPayload):
    tenant_id: str
    plan: str = "free"

auth = AuthProvider(
    config=AuthConfig(secret_key="my-secret"),
    user_loader=load_user,
    token_payload_schema=MyTokenPayload,  # JWTs will be decoded into MyTokenPayload
)

When issuing tokens, use the extra parameter to encode the custom claims into the JWT:

await auth.login(sub="user-123", extra={"tenant_id": "acme", "plan": "pro"})

On the decoding side, your user_loader will receive a MyTokenPayload instance with fully typed access to payload.tenant_id and payload.plan.

Testing your endpoints with FAuth Fakes

FAuth ships with a fauth.testing module specifically to simplify testing. No complex JWT mocks or real database dependencies needed — just use build_fake_auth_provider to wire up an in-memory auth provider.

import asyncio
import pytest
from fastapi.testclient import TestClient
from pydantic import BaseModel

from myapp.main import app, auth  # Import your FastAPI app and FAuth instance
from fauth.testing import build_fake_auth_provider

class User(BaseModel):
    id: str
    username: str
    is_active: bool = True
    roles: list[str] = []

@pytest.fixture
def test_client() -> TestClient:
    # 1. Provide a mock test user
    mock_user = User(id="user-123", username="test_user", roles=["admin"])

    # 2. Wire the fake provider with an in-memory user store
    fake = build_fake_auth_provider(users={"user-123": mock_user})

    # 3. Override the internal cached dependency functions.
    # These are the actual callables that FastAPI resolves inside Depends().
    app.dependency_overrides[auth._require_user] = fake._require_user
    app.dependency_overrides[auth._require_active_user] = fake._require_active_user

    yield TestClient(app)

    # Clean up overrides
    app.dependency_overrides.clear()

def test_secure_route(test_client):
    response = test_client.get("/me")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello test_user"}

Alternative: End-to-end testing with real JWT tokens

If you prefer issuing real tokens in tests, build_fake_auth_provider uses safe test defaults (a fixed secret key, short expiry) so you can generate tokens without any extra setup.

def test_secure_me_endpoint():
    user = User(id="user-999", username="fake_alice", roles=[])

    # FAuth supplies a pre-made test provider with safe defaults
    test_auth = build_fake_auth_provider(users={"user-999": user})

    # Generate a real JWT token via the test provider
    token_response = asyncio.run(test_auth.login(sub="user-999"))

    # Override the dependency so the app uses the test user store
    app.dependency_overrides[auth._require_user] = test_auth._require_user

    # Apply Bearer token
    client = TestClient(app)
    response = client.get(
        "/me",
        headers={"Authorization": f"Bearer {token_response.access_token}"}
    )

    assert response.status_code == 200
    assert response.json() == {"message": "Hello fake_alice"}

    app.dependency_overrides.clear()

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

fauth-0.1.1.tar.gz (56.4 kB view details)

Uploaded Source

Built Distribution

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

fauth-0.1.1-py3-none-any.whl (13.7 kB view details)

Uploaded Python 3

File details

Details for the file fauth-0.1.1.tar.gz.

File metadata

  • Download URL: fauth-0.1.1.tar.gz
  • Upload date:
  • Size: 56.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fauth-0.1.1.tar.gz
Algorithm Hash digest
SHA256 056cf41024f755a6e1fe3f89f8ecdf56164122db44aab2e3f904d30b7ff31b92
MD5 f87c165c6ba232fcdb14eb03d72aadd8
BLAKE2b-256 90d9036d95ada822727214a8b8a5ae5c75e66e1732750c6f5d40124202402bed

See more details on using hashes here.

Provenance

The following attestation bundles were made for fauth-0.1.1.tar.gz:

Publisher: cicd.yml on justmatias/fauth

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fauth-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: fauth-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 13.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fauth-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 78e16d23e1a4fe55e07167ba2409ff7884be15b381aaca42144156321700aea5
MD5 3cc90e0eb9563609021db48712572ed9
BLAKE2b-256 0bd4dcc3f967f3171025abc697a249b191ee9b15f9c454f7d753bbe1c32e7f37

See more details on using hashes here.

Provenance

The following attestation bundles were made for fauth-0.1.1-py3-none-any.whl:

Publisher: cicd.yml on justmatias/fauth

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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