Batteries-included, transport-agnostic authentication for FastAPI.
crudauth gives you one CRUDAuth object that wires cookie sessions, JWT bearer tokens, OAuth, two-factor authentication, and email flows (verify / reset / change) - with CSRF, escalating login lockout, sudo mode, and multi-device session management - over your own SQLAlchemy User model. App policy lives in hooks, not in forked dependency code. Sessions and bearer both resolve to the same Principal, so narrowing or adding a transport never changes how you authorize a route.
Features
- Transport-agnostic: cookie sessions and JWT bearer tokens behind a single
Principal; first credential present wins, and authorization code never depends on which transport authenticated. - Your model, your schema: works over your existing SQLAlchemy
Uservia a logical-fieldcolumn_map- no forced renames, no second user table. - Secure by default: synchronizer-token CSRF, escalating per-IP/per-user login lockout, bcrypt with SHA-256 pre-hash (no 72-byte truncation), timing-equalized login, and trusted-proxy IP resolution.
- OAuth and OpenID Connect: Google, GitHub, any OIDC provider from its issuer (Keycloak, Zitadel, Authentik, Auth0, Okta, Entra ID), or a custom one - with the
statebound to the initiating browser to block login CSRF. - Two-factor authentication: opt-in TOTP with encrypted secrets and single-use recovery codes, challenged from both login routes and, if you want it, from OAuth.
- Email flows: verify / reset / change - you implement the
EmailSenderport (render your own HTML fromcontext.link), the package mints and verifies the signed, single-use tokens. - Sudo mode: short-lived re-authentication to gate sensitive actions, stamped on the session and cleared on logout.
- Multi-device sessions: list, revoke one, or "sign out everywhere", with a configurable per-user session cap.
- App policy in hooks:
AuthHooksfor welcome email, trial grant, audit logging - fired uniformly across every auth path. - Pluggable backends: in-memory for dev, Redis for production - for sessions, CSRF, lockout counters, and one-time tokens.
- Fully typed & async: ships
py.typed, built on SQLAlchemy 2.0 and Pydantic v2.
Requirements
- Python 3.10+
- FastAPI, SQLAlchemy 2.0+, Pydantic v2 (installed as dependencies)
- Redis 7.0+ (or a compatible server such as Valkey) if you use the Redis backends
Install
pip install crudauth # core (session + bearer)
pip install "crudauth[all]" # + httpx (oauth), redis, user-agents, cryptography (mfa)
Or with uv:
uv add crudauth
AI agents (library skill)
crudauth ships a library skill embedded in the package, so AI coding agents follow its actual conventions and gotchas (account shapes, gates, recovery, custom email bodies, production wiring) in sync with the version you installed. After adding crudauth, install it into your project:
uvx library-skills # scans deps, links bundled skills (re-run to keep them in sync on upgrade)
uvx library-skills --claude # for Claude Code (.claude/skills)
Quickstart
Sessions are the default - no transports= needed. You get cookie auth, CSRF,
login lockout, secure cookies, and /login /logout /register /me.
from fastapi import FastAPI, Depends
from crudauth import CRUDAuth, Principal
from myapp.db import get_session
from myapp.models import User
auth = CRUDAuth(session=get_session, user_model=User, SECRET_KEY="change-me")
app = FastAPI()
app.include_router(auth.router)
@app.get("/dashboard")
async def dashboard(me: Principal = Depends(auth.current_user())):
return {"hello": me.user.username}
The user model
Inherit the mixin and get every column the package needs; your own columns coexist freely.
from sqlalchemy.orm import Mapped, mapped_column
from crudauth.models import AuthUserMixin
from myapp.db import Base
class User(Base, AuthUserMixin):
__tablename__ = "users"
full_name: Mapped[str | None] = mapped_column(default=None)
Existing table with different names? Map the contract, don't rename your schema:
auth = CRUDAuth(
session=get_session, user_model=LegacyAccount, SECRET_KEY=...,
column_map={"id": "account_id", "email": "email_address", "hashed_password": "pw_hash"},
)
Protecting routes - one factory, every case a kwarg
auth.current_user() # required, 401 if anon
auth.current_user(optional=True) # None instead of raising
auth.current_user(superuser=True) # 403 unless is_superuser
auth.current_user(verified=True) # 403 unless email_verified
auth.current_user(scopes=["reports:read"]) # 403 unless scopes ⊇ required
auth.current_user(transport="bearer") # narrow to one transport
auth.current_user(superuser=True, check=my_predicate) # extra per-route check
The resolved Principal carries user_id, is_superuser, scopes,
transport (which transport authed the request), and user (your resolved
row).
Multiple transports, one identity
from crudauth import CRUDAuth, SessionTransport, BearerTransport
auth = CRUDAuth(
session=get_session, user_model=User, SECRET_KEY=...,
redis_url=...,
transports=[
SessionTransport(csrf=True), # browsers
BearerTransport(access_ttl=900, refresh="cookie"), # apps/scripts
],
)
When both credentials are present, the first transport in the list wins. CSRF is a property of the session transport - it appears only where sessions do, never on bearer/api-key paths.
Storage & lifespan
Server-side backends open connections on startup - call initialize() /
shutdown() in your lifespan:
@asynccontextmanager
async def lifespan(app: FastAPI):
await auth.initialize()
yield
await auth.shutdown()
OAuth, MFA, email, hooks, sudo
See the usage cookbook for OAuth (Google / GitHub / any OIDC issuer / custom
providers), two-factor authentication (mfa=MfaConfig() - TOTP with recovery
codes), email flows (implement the EmailSender port; the package mints/verifies
the signed tokens), lifecycle hooks (AuthHooks - welcome email, trial grant,
audit log), sudo mode (sudo=SudoConfig() + auth.require_sudo()), and dropping
to primitives.
Architecture
crudauth is ports-and-adapters with feature slices and a single composition
root (CRUDAuth). The layering and the import-direction rules live in the
Architecture docs - read them before
adding a transport, OAuth provider, or storage backend; each is meant to be a drop-in
file, not a cross-cutting edit.
License
Contact
Benav Labs – benav.io, Discord
The Benav Labs FastAPI family
crudauth is part of a family of composable FastAPI building blocks - use whichever you need:
- FastCRUD - powerful CRUD methods and automatic endpoint creation for your SQLAlchemy models.
- CRUDAdmin - a modern, secure admin interface generated straight from your models.
- Fastro (FastAPI-boilerplate) - a batteries-included FastAPI starter: auth, CRUD, jobs, caching, and rate-limits.
- FastroAI - the complete FastAPI SaaS template: payments, entitlements, email, a frontend, and AI agents.
Build a full SaaS on FastAPI
crudauth handles authentication in FastroAI - the complete FastAPI SaaS template: auth, Stripe payments (subscriptions, credits, discounts), entitlements, transactional email, an Astro frontend, and PydanticAI agents, wired together and production-ready.
Ship your SaaS faster with FastroAI →
Metadata
Release files for crudauth 0.8.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| crudauth-0.8.2.tar.gz | 168.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| crudauth-0.8.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 390.3 kB
Release files / crudauth-0.8.2.tar.gz
| Download URL | crudauth-0.8.2.tar.gz |
|---|---|
| Size | 168.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f07983f45fd76c5fcbb1c2da2f879c7e60bd49fd628c6668f05e785783a197f1
|
|
BLAKE2b-256 checksum How to use checksums |
3eeb891b8369bb33fb7e2402c1bc4af02c166e5aef562f46772831f8b9a4b99c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / crudauth-0.8.2-py3-none-any.whl
| Download URL | crudauth-0.8.2-py3-none-any.whl |
|---|---|
| Size | 222.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
738e6a1adef2afbce38db81429ec8c936cb99e9e438f3081456783ad8c1f7ec0
|
|
BLAKE2b-256 checksum How to use checksums |
c4b44eda142f16d1c773e0cf3e8ce50d67926d47af9f3cb5f484c39b11bfcb81
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|