Skip to main content

AuthKit

Authentication as a package for FastAPI. Install it, point it at a database and a secret key, and get email/password auth, JWT tokens, and Google sign-in — without writing any of it yourself.

from fastapi import FastAPI, Depends
from authkit import AuthKit

app = FastAPI()
auth = AuthKit(app, secret_key="...", database_url="postgresql+psycopg://...")
auth.include_routes()

@app.get("/dashboard")
async def dashboard(user=Depends(auth.login_required)):
    return {"user": user.email}

Nine routes, one dependency, zero boilerplate:

/auth/register              /auth/login               /auth/refresh
/auth/logout                /auth/me                  /auth/password-reset/request
/auth/password-reset/confirm                            /auth/email/verify/request
/auth/email/verify/confirm  /auth/google/login          /auth/google/callback

Table of contents

Features

  • Email + password auth — register, login, is_active gating
  • JWT access + refresh tokens — short-lived access token, longer-lived refresh token, independent expiry configuration
  • login_required dependency — one Depends() call protects any route and injects the current user
  • Logout / token revocation — an in-memory blacklist so a logged-out token can't be reused
  • Google sign-in (OAuth 2.0) — full authorization-code flow, CSRF-protected state, account linking so a user can have both a password login and a Google login on the same account
  • Password reset — request/confirm flow with single-use, hashed, expiring tokens
  • Email verification — same token mechanism, separate purpose
  • Rate limiting — per-IP sliding window on /login and /register
  • Pluggable side effects — AuthKit never sends an email or decides how to redirect after OAuth; you supply the hook, AuthKit calls it
  • Bring your own database — any SQLAlchemy-supported engine; tables are namespaced (authkit_*) so they never collide with your app's own schema

Installation

PyPI publication is pending. Until then, install directly from GitHub:

pip install git+https://github.com/<your-username>/authkit.git

Pin to a tag once you have one (recommended for anything beyond local experimentation):

pip install git+https://github.com/<your-username>/authkit.git@v0.1.0

With Poetry, as a git dependency in pyproject.toml:

authkit = { git = "https://github.com/<your-username>/authkit.git", tag = "v0.1.0" }

Once published to PyPI, installation will be:

pip install fastapi-authstack

Quickstart

from fastapi import FastAPI, Depends
from authkit import AuthKit

app = FastAPI()

auth = AuthKit(
    app,
    secret_key="change-me-to-a-real-secret",
    database_url="sqlite:///./app.db",
)
auth.include_routes()

@app.get("/dashboard")
async def dashboard(user=Depends(auth.login_required)):
    return {"email": user.email, "verified": user.email_verified}
uvicorn main:app --reload

curl -X POST localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "supersecret123"}'

curl -X POST localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "supersecret123"}'

curl localhost:8000/dashboard \
  -H "Authorization: Bearer <access_token from login response>"

Configuration reference

auth = AuthKit(
    app,
    secret_key="...",                     # required — used to sign JWTs and OAuth state
    database_url="postgresql+psycopg://user:pass@host/db",
    algorithm="HS256",
    access_token_expires_minutes=15,
    refresh_token_expires_days=7,
    action_token_expires_minutes=30,      # password reset / email verification token TTL
    auto_create_tables=True,              # False if you manage migrations yourself
    engine_options=None,                  # forwarded to sqlalchemy.create_engine
    google_oauth={
        "client_id": "...",
        "client_secret": "...",
        "redirect_uri": "https://yourapp.com/auth/google/callback",
    },
    on_password_reset_requested=lambda user, token: send_email(user, token),
    on_email_verification_requested=lambda user, token: send_email(user, token),
    on_oauth_login_complete=lambda user, tokens, redirect_after: my_redirect(user, tokens, redirect_after),
)
Parameter Required Default Notes
secret_key ✅ — Signs JWTs and OAuth state tokens
database_url ✅ — Any SQLAlchemy-compatible URL
google_oauth — None Omit entirely to disable Google sign-in — its routes won't be mounted
auto_create_tables — True Set False and run your own migration if you use Alembic
on_password_reset_requested — no-op (user, raw_token) -> None — send the email yourself
on_email_verification_requested — no-op Same shape as above
on_oauth_login_complete — redirect with base64 payload (user, tokens, redirect_after) -> Response

API reference

Method Path Auth required Description
POST /auth/register No Create an account, returns user + token pair
POST /auth/login No Authenticate, returns user + token pair
POST /auth/refresh No (refresh token) Exchange a refresh token for a new pair
POST /auth/logout Yes Revoke the current access token
GET /auth/me Yes Return the current user
POST /auth/password-reset/request No Issue a password reset token via the configured hook
POST /auth/password-reset/confirm No (reset token) Consume the token, set a new password
POST /auth/email/verify/request Yes Issue an email verification token via the configured hook
POST /auth/email/verify/confirm No (verification token) Consume the token, mark the email verified
GET /auth/google/login No Redirect to Google's consent screen
GET /auth/google/callback No Handle Google's callback, create/link the user, redirect

All error responses are {"detail": "..."} with the appropriate HTTP status — 401 for bad credentials/tokens, 403 for inactive accounts, 409 for duplicate registration, 429 for rate limiting.

System design

AuthKit is a single facade (AuthKit) composing four independent layers. Each layer only depends on the layers below it — there are no upward or circular imports, which keeps every piece independently testable and replaceable.

                      ┌───────────────────────────────────────────┐
                      │                AuthKit (core.py)            │
                      │   the only class an integrator touches       │
                      └───────────────────┬───────────────────────┘
                                          │ wires together
        ┌─────────────────────┬─────────┼─────────────────┬─────────────────────┐
        ▼                     ▼                             ▼                     ▼
┌───────────────┐   ┌──────────────────┐        ┌──────────────────┐   ┌──────────────────┐
│    routes/      │   │    services/       │        │      oauth/        │   │  dependencies.py  │
│  HTTP contracts  │──▶│  business logic    │        │  provider adapters  │   │  FastAPI Depends()  │
│  auth.py         │   │  AuthService        │        │  base / google /    │   │  login_required     │
│  google.py       │   │  TokenService       │        │  state / redirect   │   │  get_session         │
└───────────────┘   │  ActionTokenService │        └──────────────────┘   └──────────────────┘
                      └─────────┬──────────┘
                                │ reads/writes through
                                ▼
                      ┌──────────────────┐
                      │  repositories/      │
                      │  UserRepository     │   ← the only layer that writes SQL
                      └─────────┬──────────┘
                                ▼
                      ┌──────────────────┐
                      │     models.py        │   User · UserIdentity · ActionToken
                      └─────────┬──────────┘
                                ▼
                      ┌──────────────────┐
                      │      db.py            │   SQLAlchemy engine + session factory
                      └──────────────────┘

  cross-cutting, used by routes/services directly:
  config.py (typed settings) · security.py (hashing + JWT primitives)
  exceptions.py (typed errors → HTTP status) · rate_limit.py · token_blacklist.py

Why layered this way:

  • Routes never touch the database. They validate input, call a service, and shape the HTTP response. All persistence goes through repositories/.
  • Services own business rules, not SQL or HTTP. AuthService.authenticate doesn't know it's being called from a FastAPI route — it just raises a typed exception on failure.
  • One exception hierarchy (AuthKitError) maps to HTTP status codes in one place — a single @app.exception_handler(AuthKitError) registered in core.py. No route handler contains a raw HTTPException.
  • OAuth providers implement one interface (oauth/base.py: OAuthProvider). Google is the only implementation today; adding GitHub or Microsoft is a new file in oauth/, not a change to core.py or the route layer.
  • Every side effect the host application needs to control is a hook, not a hardcoded integration. AuthKit does not send emails or decide how to redirect after OAuth login — it calls on_password_reset_requested, on_email_verification_requested, and on_oauth_login_complete, which the integrator supplies. AuthKit's job is auth, not email delivery.

Data model

  • User — one row per account. hashed_password is nullable, because an OAuth-only user has no password.
  • UserIdentity — links a User to a third-party provider (provider, provider_user_id). A user can have a password login and a Google login on the same account, matching how real products behave.
  • ActionToken — single-use, hashed, expiring tokens for password reset and email verification, keyed by purpose.

Tables are prefixed authkit_* so they never collide with the host application's own schema.

Roadmap

Deliberately out of scope for v1, planned as the next layer on top of this one:

  • Two-factor authentication (TOTP)
  • API keys (create, scope, rotate, revoke)
  • Roles & permissions
  • Multi-tenant organizations (teams, invitations, membership)
  • Additional OAuth providers (GitHub, Microsoft)
  • Persistent token blacklist (Redis-backed, for multi-process deployments)

Development

Requires Python 3.11+.

git clone https://github.com/<your-username>/authkit.git
cd authkit
python3 -m venv venv && source venv/bin/activate
pip install -e .
pip install pytest
pytest

The test suite runs against an in-memory SQLite database — no external services required.

Contributing

Issues and pull requests are welcome. Before opening a PR:

  1. Run pytest and make sure everything passes
  2. Keep the layering intact — routes call services, services call repositories, nothing skips a layer
  3. Match the existing style: no comments, self-explanatory names, typed function signatures

License

MIT — see LICENSE.

Metadata

Release files for fastapi-authstack 0.1.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-authstack 0.1.0
File Size Uploaded
fastapi_authstack-0.1.0.tar.gz 19.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fastapi-authstack 0.1.0
File Interpreter ABI Platform
fastapi_authstack-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 41.8 kB

Release files / fastapi_authstack-0.1.0.tar.gz

Download URL fastapi_authstack-0.1.0.tar.gz
Size 19.4 kB
Tags Source
SHA-256 checksum
How to use checksums
e53fdb23b5b66e4f724957aacda1d60b4ef1eff190833844df063e88cbb9f461
BLAKE2b-256 checksum
How to use checksums
31d4a50598eeb21ed466b2d952fe13e69d09a37a5215298f9165f49fcd44d88c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.2 CPython/3.8.10 Linux/5.15.0-139-generic

Release files / fastapi_authstack-0.1.0-py3-none-any.whl

Download URL fastapi_authstack-0.1.0-py3-none-any.whl
Size 22.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8f5438182a501d6efbae6636957f231ac13a0ebde2a366d39c083be11bffe25b
BLAKE2b-256 checksum
How to use checksums
f923f37e74f613693fca841a10246d851efc3d9a1a70cec0181b913911e49dbc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.2 CPython/3.8.10 Linux/5.15.0-139-generic

Release history Release notifications | RSS feed

This release

0.1.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