A production-grade, pluggable authentication library for FastAPI — JWT, OAuth2 (8 providers), MFA, RBAC, and full flow flexibility (email/SMS, link/OTP, multi-identifier login).
Project description
authwarden
Production-grade FastAPI authentication and authorization library. Wraps proven cryptographic libraries — never rolls its own.
Features
- JWT authentication — access + refresh tokens, rotation, per-token revocation via
jti - Password hashing — argon2 (default) or bcrypt via
pwdlib, auto-rehash on login - Full auth flows — register, verify email, login, logout, forgot/reset/change password
- MFA — TOTP setup/confirm/disable (pyotp), hashed backup codes
- OAuth 2.0 / Social login — Google, GitHub, Facebook, Apple, Twitter/X, Microsoft, LinkedIn, Discord
- RBAC — role hierarchy, scope guards,
Depends()factories - Session management — pluggable backends (in-memory, Redis)
- Email — SMTP + console backends, HTML + plain-text templates for every flow
- Plug-and-play router — mount all endpoints with one line
- Storage-agnostic — implement
AbstractUserStorefor any ORM or database
Installation
pip install authwarden
With Redis session support:
pip install authwarden[redis]
Quickstart
from fastapi import FastAPI, Depends
from authwarden import AuthWarden, WardenConfig
from authwarden.storage.memory import MemoryUserStore
app = FastAPI()
warden = AuthWarden(
config=WardenConfig(secret_key="your-secret-key"),
user_store=MemoryUserStore(),
)
# Mount all auth endpoints under /auth
app.include_router(warden.router, prefix="/auth", tags=["auth"])
# Protect your own routes
@app.get("/profile")
async def profile(user=Depends(warden.current_user)):
return user
@app.delete("/admin/users/{user_id}")
async def delete_user(user=Depends(warden.require_roles("admin"))):
...
This mounts:
POST /auth/register
POST /auth/verify-email
POST /auth/resend-verification
POST /auth/login
POST /auth/logout
POST /auth/refresh
POST /auth/forgot-password
POST /auth/reset-password
POST /auth/change-password
POST /auth/mfa/setup
POST /auth/mfa/confirm
POST /auth/mfa/disable
GET /auth/oauth/{provider}/authorize
POST /auth/oauth/{provider}/callback
POST /auth/oauth/{provider}/connect
DEL /auth/oauth/{provider}/disconnect
GET /auth/oauth/accounts
POST /auth/set-password
Configuration
from authwarden import WardenConfig
config = WardenConfig(
# Required
secret_key="a-long-random-secret",
# JWT
algorithm="HS256",
access_token_ttl=900, # 15 minutes
refresh_token_ttl=604800, # 7 days
enable_refresh_rotation=True,
# Passwords
password_hasher="argon2", # or "bcrypt"
min_password_length=8,
require_password_uppercase=False,
require_password_digit=False,
require_password_special=False,
# Email
email_backend="smtp", # or "console" for dev
smtp_host="smtp.example.com",
smtp_port=587,
smtp_username="user@example.com",
smtp_password="...",
emails_from_address="noreply@example.com",
# Registration
require_email_verification=True,
allow_registration=True,
# MFA
enable_mfa=True,
mfa_issuer_name="MyApp",
# Session (optional)
session_backend="redis",
redis_url="redis://localhost:6379",
# Frontend URLs (for email links)
frontend_base_url="https://myapp.com",
verify_email_path="/auth/verify-email",
reset_password_path="/auth/reset-password",
)
All settings can also be loaded from environment variables (via pydantic-settings):
SECRET_KEY=your-secret-key
EMAIL_BACKEND=smtp
SMTP_HOST=smtp.example.com
Custom User Store
Implement AbstractUserStore to connect any database:
from authwarden.storage.base import AbstractUserStore
from authwarden.models.user import UserInDB
class SQLAlchemyUserStore(AbstractUserStore):
async def get_by_id(self, user_id: str) -> UserInDB | None:
...
async def get_by_email(self, email: str) -> UserInDB | None:
...
async def create(self, user: UserInDB) -> UserInDB:
...
async def update(self, user: UserInDB) -> UserInDB:
...
async def delete(self, user_id: str) -> None:
...
OAuth / Social Login
from authwarden.authentication.oauth import OAuthProviderConfig
warden = AuthWarden(
config=WardenConfig(
secret_key="...",
oauth_providers={
"google": OAuthProviderConfig(
client_id="...",
client_secret="...",
redirect_uri="https://myapp.com/auth/oauth/google/callback",
),
"github": OAuthProviderConfig(
client_id="...",
client_secret="...",
redirect_uri="https://myapp.com/auth/oauth/github/callback",
),
}
),
user_store=MyUserStore(),
)
Apple Sign In requires additional fields:
config = WardenConfig(
...
apple_team_id="TEAM123",
apple_key_id="KEY123",
apple_private_key_pem="-----BEGIN PRIVATE KEY-----\n...",
oauth_providers={
"apple": OAuthProviderConfig(
client_id="com.myapp.service",
client_secret="", # auto-generated from private key
redirect_uri="https://myapp.com/auth/oauth/apple/callback",
)
}
)
MFA (TOTP)
# Enable globally
config = WardenConfig(secret_key="...", enable_mfa=True)
# Endpoints are automatically mounted:
# POST /auth/mfa/setup → returns { secret, qr_uri, backup_codes }
# POST /auth/mfa/confirm → activates MFA after verifying first code
# POST /auth/mfa/disable → requires password + TOTP or backup code
Login with MFA:
POST /auth/login
{
"email": "user@example.com",
"password": "hunter2",
"totp_code": "123456"
}
RBAC
from fastapi import Depends
# Require a single role
@app.get("/admin")
async def admin(user=Depends(warden.require_roles("admin"))):
...
# Require any of multiple roles
@app.get("/reports")
async def reports(user=Depends(warden.require_roles("admin", "analyst"))):
...
# Require a scope
@app.post("/items")
async def create_item(user=Depends(warden.require_scopes("items:write"))):
...
Email Templates
Override any template by subclassing EmailTemplates:
from authwarden.email.templates import EmailTemplates
class MyTemplates(EmailTemplates):
def verify_email(self, user, link: str) -> tuple[str, str, str]:
# returns (subject, plain_text, html)
return (
"Verify your account",
f"Click here: {link}",
f"<a href='{link}'>Verify your account</a>",
)
warden = AuthWarden(config=..., user_store=..., email_templates=MyTemplates())
Auth Flows Reference
| Flow | Endpoint | Auth required |
|---|---|---|
| Register | POST /auth/register |
No |
| Verify email | POST /auth/verify-email |
No |
| Resend verification | POST /auth/resend-verification |
No |
| Login | POST /auth/login |
No |
| Logout | POST /auth/logout |
Bearer token |
| Refresh token | POST /auth/refresh |
No |
| Forgot password | POST /auth/forgot-password |
No |
| Reset password | POST /auth/reset-password |
No |
| Change password | POST /auth/change-password |
Bearer token |
| MFA setup | POST /auth/mfa/setup |
Bearer token |
| MFA confirm | POST /auth/mfa/confirm |
Bearer token |
| MFA disable | POST /auth/mfa/disable |
Bearer token |
| OAuth authorize | GET /auth/oauth/{provider}/authorize |
No |
| OAuth callback | POST /auth/oauth/{provider}/callback |
No |
| Connect provider | POST /auth/oauth/{provider}/connect |
Bearer token |
| Disconnect provider | DELETE /auth/oauth/{provider}/disconnect |
Bearer token |
| List linked accounts | GET /auth/oauth/accounts |
Bearer token |
| Set password (OAuth) | POST /auth/set-password |
Bearer token |
Security Notes
- Passwords hashed with argon2 by default (bcrypt available)
- JWT tokens include a
jti(UUID) claim — enables per-token revocation - Password reset and email verification tokens stored as hashes only
- Forgot password and resend verification always return
200(anti-enumeration) - Constant-time comparison used for all token lookups (
hmac.compare_digest) - PKCE (S256) used for all OAuth flows
- Refresh token rotation enabled by default — old
jtiblacklisted on use - MFA backup codes stored as argon2 hashes, single-use
- OAuth provider tokens encrypted at rest (Fernet)
Development
git clone https://github.com/yourusername/authwarden.git
cd authwarden
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
Running Tests
# All tests
pytest
# With coverage
pytest --cov=authwarden --cov-report=term-missing
# Specific suite
pytest tests/test_oauth.py -v
Project Structure
authwarden/
├── core/ # WardenConfig, AuthWarden facade, request context
├── authentication/ # JWT, password hashing, OAuth provider base
├── flows/ # One module per auth flow
├── mfa/ # TOTP + backup codes
├── permissions/ # Roles + scope guards
├── session/ # Memory + Redis session backends
├── dependencies/ # FastAPI Depends() factories
├── routers/ # Plug-and-play FastAPI routers
├── email/ # SMTP, console backends + templates
├── models/ # Pydantic v2 models
└── storage/ # AbstractUserStore + MemoryUserStore
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file authwarden-0.7.1.tar.gz.
File metadata
- Download URL: authwarden-0.7.1.tar.gz
- Upload date:
- Size: 81.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9e3d28bffc54846c7d86098555f34e7fde12ee1c7ca93c168fe38a40f5c480e
|
|
| MD5 |
bbb45a54ee13d173b6c6118434ce41bb
|
|
| BLAKE2b-256 |
bb5ec2d7828bd222939fba97b9f28d82db38cbc0c1fa86d385c65448d4482c2b
|
Provenance
The following attestation bundles were made for authwarden-0.7.1.tar.gz:
Publisher:
publish.yml on timihack/authwarden
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
authwarden-0.7.1.tar.gz -
Subject digest:
b9e3d28bffc54846c7d86098555f34e7fde12ee1c7ca93c168fe38a40f5c480e - Sigstore transparency entry: 1980804177
- Sigstore integration time:
-
Permalink:
timihack/authwarden@d8dcd770015b4611f260ee1394281df74c5cd75d -
Branch / Tag:
refs/tags/v.0.7.1 - Owner: https://github.com/timihack
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d8dcd770015b4611f260ee1394281df74c5cd75d -
Trigger Event:
release
-
Statement type:
File details
Details for the file authwarden-0.7.1-py3-none-any.whl.
File metadata
- Download URL: authwarden-0.7.1-py3-none-any.whl
- Upload date:
- Size: 84.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d3ac743eec2e81f7d99625138515f042969499caa1b8ad876db1811dcc8afe09
|
|
| MD5 |
3ce8c58072bc6bb22a62fe021a71929f
|
|
| BLAKE2b-256 |
716543d5959d223ad0452147a388e9e4375f56e45d4bec2605b477261ff9dcac
|
Provenance
The following attestation bundles were made for authwarden-0.7.1-py3-none-any.whl:
Publisher:
publish.yml on timihack/authwarden
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
authwarden-0.7.1-py3-none-any.whl -
Subject digest:
d3ac743eec2e81f7d99625138515f042969499caa1b8ad876db1811dcc8afe09 - Sigstore transparency entry: 1980804376
- Sigstore integration time:
-
Permalink:
timihack/authwarden@d8dcd770015b4611f260ee1394281df74c5cd75d -
Branch / Tag:
refs/tags/v.0.7.1 - Owner: https://github.com/timihack
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d8dcd770015b4611f260ee1394281df74c5cd75d -
Trigger Event:
release
-
Statement type: