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.0.tar.gz (56.5 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.0-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: fauth-0.1.0.tar.gz
  • Upload date:
  • Size: 56.5 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.0.tar.gz
Algorithm Hash digest
SHA256 6438efe9847f52edfed8f30cb64658173c1f616ee0596d6b88bdcb70773588bc
MD5 76186c3292f99c0d6b1f0b96928240fc
BLAKE2b-256 29a6aaec21f952bb724c2224bfdc2e1c91fcf4b663bb6ac9bd9a7c6dac038b6c

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: fauth-0.1.0-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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7bcf8858b128017c47da01c782af29c6e49363b6c4549ac2436cd2e9f52616db
MD5 fb36d14c2d3e1e502147bb6ed60647d0
BLAKE2b-256 ebfef09f2079394dba0841f3767a978069370d5ec624b544760a2732e0140992

See more details on using hashes here.

Provenance

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