Skip to main content

fastapi-auth-oauth

Authentication as a package for FastAPI — email/password, JWT, and Google OAuth in one install.

PyPI version Python versions License Downloads CI

Install with pip install fastapi-auth-oauth, import as authkit.

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 — blacklist so a logged-out token can't be reused; in-memory by default, Redis-backed for multi-worker deployments (token_blacklist="redis://...")
  • Cookie or bearer transport — tokens via Authorization: Bearer (default), HttpOnly cookies with double-submit CSRF protection, or both
  • 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
  • Sync and async SQLAlchemy — pass an async URL (postgresql+asyncpg://, sqlite+aiosqlite://) and every route and DB call runs natively async; no config flag needed

Installation

pip install fastapi-auth-oauth

# with Redis-backed token blacklist support:
pip install "fastapi-auth-oauth[redis]"

The PyPI distribution name is fastapi-auth-oauth — but you always import it as authkit:

from authkit import AuthKit

To install straight from the latest commit on GitHub instead (e.g. to try unreleased changes):

pip install git+https://github.com/arslanfayyaz/authkit-.git

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
    token_blacklist="memory",             # or "redis://localhost:6379/0", or a custom backend
    token_transport="bearer",             # or "cookie" (HttpOnly cookies + CSRF) or "both"
    cookie_secure=True,                   # cookie mode: only send cookies over HTTPS
    cookie_samesite="lax",
    cookie_domain=None,
    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. An async driver (+asyncpg, +aiosqlite) switches the whole stack to native async — with auto_create_tables=True tables are then created on app startup
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
token_blacklist — "memory" "memory", a redis:// URL, or any object with revoke()/is_revoked(). Use Redis when running more than one worker
token_transport — "bearer" "bearer", "cookie", or "both". Cookie mode sets HttpOnly access/refresh cookies and enforces double-submit CSRF
cookie_secure / cookie_samesite / cookie_domain — True / "lax" / None Cookie attributes (cookie mode only)
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

Cookie mode

With token_transport="cookie" (or "both"), login/register/refresh set three cookies: authkit_access and authkit_refresh (HttpOnly — invisible to JavaScript) and authkit_csrf (readable by JavaScript on purpose). The browser then authenticates requests automatically; no Authorization header needed.

For any state-changing request (POST/PUT/PATCH/DELETE) authenticated via cookie, the client must echo the CSRF cookie back in a header — the standard double-submit pattern:

fetch("/auth/logout", {
  method: "POST",
  headers: { "X-CSRF-Token": getCookie("authkit_csrf") },
});

The refresh cookie is path-scoped to the /auth/refresh endpoint, so it is never sent anywhere else. POST /auth/refresh with no body uses it automatically.

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 (body, or cookie in cookie mode) for a new pair
POST /auth/logout Yes Revoke the current access token; pass {"refresh_token": ...} to revoke the pair. Clears auth cookies in cookie mode
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) — shipped in 0.2.0
  • Cookie token transport with CSRF protection — shipped in 0.2.0
  • Async SQLAlchemy support — shipped in 0.3.0
  • Google service integrations — Google Ads, Google Business Profile (GMB), and Local Services Ads (LSA) account linking on top of the Google OAuth flow

Development

Requires Python 3.11+.

git clone https://github.com/arslanfayyaz/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-auth-oauth 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-auth-oauth 0.3.0
File Size Uploaded
fastapi_auth_oauth-0.3.0.tar.gz 26.4 kB Details

Built distribution (wheel)

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

Total release size: 59.0 kB

Release files / fastapi_auth_oauth-0.3.0.tar.gz

Download URL fastapi_auth_oauth-0.3.0.tar.gz
Size 26.4 kB
Tags Source
SHA-256 checksum
How to use checksums
c18fe32065e6859e34912af00e5ccbdce762adce0d6cdc22e283aa6e5f275804
BLAKE2b-256 checksum
How to use checksums
ecbbe8ba1b5ccb95ae0441ce8ff6a78e687666db3dce21036082abaacf61c291
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_auth_oauth-0.3.0-py3-none-any.whl

Download URL fastapi_auth_oauth-0.3.0-py3-none-any.whl
Size 32.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5cba7106de7fef9511654904ad450b99f329c563842be7a753e5f30089b8719e
BLAKE2b-256 checksum
How to use checksums
3b919099b0eff08b17b1d5512f5a7dd46885e22e106f419079f6832e97e59a1d
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.3.0 This release

2 release files

0.1.1

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