Skip to main content

fastapi-startkit-auth

Passport-style OAuth2 + JWT authentication for FastAPI.

fastapi-startkit-auth brings the ergonomics of Laravel Passport to FastAPI: a config-driven guard/provider/passwords model layered on top of OAuth2 grants and signed JWT access tokens (per the FastAPI security tutorial).

Features

Area What you get
Config layer AuthConfig (default / guards / providers / passwords) + AuthProvider that registers into the app
Password grant OAuth2 password grant → signed JWT access tokens with expiry (/oauth/token, /token)
Refresh tokens Opaque refresh tokens with rotation and configurable TTL
Personal access tokens Named, long-lived tokens with scopes/abilities
Client credentials Machine-to-machine grant
Authorization code + PKCE Browser/SPA flow with S256 & plain PKCE
Clients Register / list / delete confidential & public clients
Revocation & introspection RFC 7009 revoke + RFC 7662 introspect
Password reset password_reset_tokens flow with configurable expire + throttle
Guards & deps current_user, optional_user, require_scopes FastAPI dependencies
Pluggable providers In-memory, generic ORM/masoniteorm model, or any custom UserProvider

Installation

pip install fastapi-startkit-auth
# optional ORM provider driver
pip install "fastapi-startkit-auth[masoniteorm]"

Uses bcrypt directly (not passlib, which imports the crypt stdlib module removed in Python 3.13+), so it runs on modern Python.

Quickstart

from fastapi import Depends
from fastapi_startkit_auth import Application, AuthProvider, AuthConfig, current_user, require_scopes
from myapp.models import User  # any active-record-style model


class Config(AuthConfig):
    key = "change-me-to-a-long-random-secret"   # JWT signing key

    default = {"guard": "api", "passwords": "users"}
    guards = {"api": {"driver": "passport", "provider": "users"}}
    providers = {"users": {"driver": "masoniteorm", "model": User}}
    passwords = {
        "users": {"provider": "users", "table": "password_reset_tokens",
                  "expire": 60, "throttle": 60},
    }


app = Application([(AuthProvider, Config)])
api = app.api  # the underlying FastAPI instance


@api.get("/me")
def me(user=Depends(current_user)):
    return user


@api.get("/reports")
def reports(ctx=Depends(require_scopes("reports:read"))):
    return {"ok": True}

Serve it:

uvicorn myapp:app        # Application is ASGI-callable
uvicorn myapp:app.api    # or serve the FastAPI instance directly

Configuration

AuthConfig mirrors Laravel's config/auth.php and adds JWT/token knobs:

class AuthConfig:
    default   = {"guard": "api", "passwords": "users"}
    guards    = {"api": {"driver": "passport", "provider": "users"}}
    providers = {"users": {"driver": "masoniteorm", "model": User}}
    passwords = {"users": {"provider": "users", "table": "password_reset_tokens",
                            "expire": 60, "throttle": 60}}

    # token settings (all optional, sensible defaults shown)
    key                        = None          # JWT secret (required in production)
    algorithm                  = "HS256"
    access_token_ttl           = 3600          # seconds
    refresh_token_ttl          = 60 * 60 * 24 * 14
    personal_access_token_ttl  = 60 * 60 * 24 * 365
    authorization_code_ttl     = 600
    bcrypt_rounds              = 12

Provider drivers

driver meaning
masoniteorm / orm / model wrap a model exposing find(id) and where(field, value).first()
memory in-memory dict store (users=[...]) — great for tests/demos
instance pass a ready UserProvider via {"instance": ...}
factory pass a zero-arg callable returning a UserProvider

Any object implementing the UserProvider protocol (retrieve_by_id, retrieve_by_credentials, validate_credentials, get_identifier, update_password) is a valid provider.

HTTP endpoints

Method & path Purpose
POST /oauth/token Unified token endpoint: password, refresh_token, client_credentials, authorization_code
POST /token Simple password grant (FastAPI-tutorial style)
POST /oauth/authorize Approve an auth-code request (requires an authenticated user) → returns code
POST /oauth/introspect RFC 7662 token introspection (requires client authentication)
POST /oauth/revoke RFC 7009 access/refresh token revocation (requires client authentication)
POST/GET /oauth/clients, DELETE /oauth/clients/{id} Client registration & management
POST/GET /oauth/personal-access-tokens, DELETE .../{jti} Personal access tokens
POST /password/email Trigger a password-reset token (delivered out-of-band; see below)
POST /password/reset Reset the password with a token

Password resets

POST /password/email always returns the same generic response whether or not the account exists (no user enumeration) and never puts the token in the response body. Configure how the token reaches the user with a notifier:

class AuthConfig(BaseAuthConfig):
    password_reset_notifier = staticmethod(lambda email, token: send_email(email, token))
    # debug_expose_reset_token = True   # DEV ONLY: echo the token in the response

The code_challenge for public clients is mandatory — a public (secretless) client cannot obtain an authorization code without PKCE, and codes are verified against the code_verifier at exchange.

Example: password grant

curl -X POST localhost:8000/oauth/token \
  -d grant_type=password -d username=ada@example.com -d password=secret -d scope="read write"
# → { "access_token": "...", "token_type": "Bearer", "expires_in": 3600,
#     "refresh_token": "...", "scope": "read write" }

Example: authorization code + PKCE

  1. POST /oauth/authorize with a bearer token and code_challenge → returns a single-use code.
  2. POST /oauth/token with grant_type=authorization_code, the code, and the code_verifier.

Scopes / abilities

Tokens carry scopes; enforce them with the require_scopes dependency:

require_scopes("posts:write")               # must have this scope
require_scopes("a", "b")                     # must have all
require_scopes("a", "b", mode="any")         # must have at least one

* is a wildcard scope that satisfies any check. Inside a handler you can also inspect the AuthContext (ctx.can(...), ctx.can_any(...), ctx.scopes).

Testing

pip install -e ".[test]"
pytest

Or with uv:

uv sync --group dev
uv run pytest

Releasing

Releases are cut from main with the release script (maintainers only):

./bin/release.sh          # patch bump
./bin/release.sh minor    # or: major

The script bumps the version, builds sdist + wheel, validates them with twine check, uploads to PyPI, then commits, tags vX.Y.Z, and creates a GitHub release. It requires uv, gh (authenticated), and PyPI credentials for twine (e.g. a pypi-* API token via TWINE_USERNAME=__token__ / TWINE_PASSWORD or ~/.pypirc).

License

MIT

Metadata

Release files for fastapi-startkit-auth 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastapi-startkit-auth 0.3.0
File Size Uploaded
fastapi_startkit_auth-0.3.0.tar.gz 39.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fastapi-startkit-auth 0.3.0
File Interpreter ABI Platform
fastapi_startkit_auth-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 101.4 kB

Release files / fastapi_startkit_auth-0.3.0.tar.gz

Download URL fastapi_startkit_auth-0.3.0.tar.gz
Size 39.0 kB
Tags Source
SHA-256 checksum
How to use checksums
6019971c9301c934d3a7d6c6c8ed8b8f27b4fd2a6d4398cc035ad0511a3d67b1
BLAKE2b-256 checksum
How to use checksums
ba073e9170d048b1e4a12e513444e05010f3ff053d9c7ef6d02994350cb1632b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.7

Release files / fastapi_startkit_auth-0.3.0-py3-none-any.whl

Download URL fastapi_startkit_auth-0.3.0-py3-none-any.whl
Size 62.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
87ce25c4253c045d400b2260567a7a48b6cbe685a254b57675db74e104cb2c1e
BLAKE2b-256 checksum
How to use checksums
fa3ac8667489f5221e9c0a80e7cf55201e74bd484e2d742700f184cb4b7cdd07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.7

Release history Release notifications | RSS feed

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

This release

0.3.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page