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.
  • Automatic OpenAPI/Swagger UI Support: Integrated security schemes that automatically show the "Authorize" button and security lock icons in Swagger UI.
  • Built-in Password Hashing & Crypto: Uses modern Argon2 via pwdlib and includes utilities for creating/decoding JWT access and refresh tokens.
  • 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
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():
    # 4. Use `auth.login` to issue tokens (password verification omitted)
    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 with list of roles
    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.
# This also enables the "Authorize" button in Swagger UI!
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 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 dependency functions.
    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"}

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.

@pytest.mark.asyncio
async 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 = await 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.2.tar.gz (56.9 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.2-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: fauth-0.1.2.tar.gz
  • Upload date:
  • Size: 56.9 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.2.tar.gz
Algorithm Hash digest
SHA256 03b38797331b1413e68031c66ef2285d44804ed5ff48220ef4d93efb2dd42c6d
MD5 5bbce140392d25309ab109640fc8adfe
BLAKE2b-256 fa3334e5bdffc5f1d12e2e28e9aa407d0f1635975568128a5024445709fe7f40

See more details on using hashes here.

Provenance

The following attestation bundles were made for fauth-0.1.2.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.2-py3-none-any.whl.

File metadata

  • Download URL: fauth-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 14.1 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a27fdb180f17e813c4d3c87768ebdd7d395a1f5e8dfab6b3b772db0c2ff62333
MD5 1a0513576b3840f22026c82cf574ae48
BLAKE2b-256 c3777d4f6029c63cd732c816c6c8de3cd13d1cdb0cf3f0ce8a3b930cb8fcedd6

See more details on using hashes here.

Provenance

The following attestation bundles were made for fauth-0.1.2-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