Release v0.19.0 - The crudauth Migration
Release Notes by the Fastro FastAPI boilerplate team
This release is mostly about replacing the authentication code with crudauth. About 6,000 lines of security-critical code you no longer have to maintain.
If you're on v0.18.0 and you don't reach into infrastructure/auth/ internals, this upgrade is close to transparent: /login, /logout, /oauth/google, /refresh-csrf, and /check-auth behave the same, get_current_user returns the same dict, and a cookie-based frontend needs no changes. If you imported from the old infrastructure.auth.session.* modules or relied on the JWT-era settings, see the breaking changes below.
Why hand auth off to a library
The forked auth was the boilerplate's single largest source of subtle, security-relevant surface: token handling, CSRF double-submit, escalating lockout, session storage across three backends. It worked, but every line of it was ours to keep correct, and none of it was the boilerplate's actual job.
crudauth does exactly this job: it's the auth library extracted from fastroai-template (the same lineage as the v0.18.0 restructure), it's been running real apps under load, and it's transport-agnostic: the same Principal resolves a request whether it authenticates by cookie session or, if you opt in, a JWT bearer token. The trade is a third-party dependency in exchange for deleting ~6,000 lines and inheriting a maintained, independently-tested auth core. For a boilerplate whose whole pitch is "use what you need, drop what you don't", auth is the one place where rolling your own is rarely the right call.
What's New in v0.19.0
Three changes, one of them the headline:
- Auth runs on crudauth. A single
auth = CRUDAuth(...)composition root ininfrastructure/auth/setup.pyreplaces the vendored session manager, the OAuth framework, and the password utilities. The route handlers drive crudauth's building blocks directly, so the URLs and response shapes are unchanged. - Annotated type-alias dependency injection (#261). Route signatures moved from inline
Depends(...)to centralizedAnnotated[..., Depends(...)]aliases, with per-moduledependencies.pyfiles holding the service aliases. - Env-configurable app metadata.
APP_NAME,APP_DESCRIPTION, andVERSIONare now read from the environment.
Plus the smaller wins that come with crudauth: login lockout now returns 429 + Retry-After instead of a generic failure, a TRUSTED_PROXY_HOPS setting for correct client-IP resolution behind a proxy, and a derived User.is_active (not is_deleted) so soft-deleted users can't authenticate.
Breaking Changes Summary
| Change | Impact | Migration Effort |
|---|---|---|
| Forked auth modules deleted | Imports from infrastructure.auth.session.* / auth.oauth.* break at import time |
Medium — repoint to infrastructure.auth.dependencies |
get_password_hash / verify_password moved |
Old import path fails | Low — import from crudauth |
| Memcached session backend removed | SESSION_BACKEND=memcached is invalid |
Low — use redis or memory |
| JWT-era settings removed | ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES, REFRESH_TOKEN_EXPIRE_DAYS, SESSION_COOKIE_MAX_AGE, LOGIN_MAX_ATTEMPTS, LOGIN_WINDOW_MINUTES no longer exist |
Low — delete from env; add TRUSTED_PROXY_HOPS if behind a proxy |
| Login lockout response | Now 429 + Retry-After (was a generic 401) |
Low — handle 429 client-side |
| GitHub OAuth provider removed | Only Google is wired | Low — re-enable via crudauth's OAuthProviderFactory |
| Password hash format | crudauth uses bcrypt with a SHA-256 pre-hash; existing hashes won't validate | High for existing DBs — rehash on next login; none for fresh installs |
Detailed Changes
1. Auth on crudauth ⚠️ BREAKING
The boilerplate carried its own SessionManager, an OAuth provider framework, password utilities, and three session storage backends. All of it duplicated what crudauth already does. This release introduces a single composition root — auth = CRUDAuth(...) in infrastructure/auth/setup.py, constructed at import time and wired into the app lifespan via auth.initialize() / auth.shutdown() — and routes the app through it.
The six existing routes keep their paths and response shapes; their bodies now call crudauth's building blocks. infrastructure/auth/dependencies.py resolves each request to a crudauth Principal and layers the dict-compatible get_current_user / get_optional_user / get_current_superuser on top, re-loading the row so the byte-identical user dict the handlers and the API-key module expect is preserved. Because the boilerplate's User already has every column crudauth needs, no column_map is required.
The vendored engine was then deleted wholesale: infrastructure/auth/{session,oauth}/, auth/utils.py, and auth/constants.py — about 6,000 lines. auth/http_exceptions.py is kept (it only re-exports FastCRUD exceptions). Password hashing now imports from crudauth.
2. Annotated type-alias dependency injection (#261)
Route signatures moved from inline Depends(...) to centralized Annotated[..., Depends(...)] aliases in infrastructure/dependencies.py, with per-module dependencies.py files (user, tier, rate_limit, api_keys) holding the service aliases. Adding a new endpoint no longer means repeating the dependency boilerplate. Landed by co-maintainer @emiliano-go.
3. Env-configurable app metadata
APP_NAME, APP_DESCRIPTION, and VERSION are now read from the environment via config(...) instead of being hardcoded, so a fork can rename itself without editing settings.
4. Settings, lockout, and the memcached drop ⚠️ BREAKING
Login lockout is now crudauth's escalating per-IP / per-identifier throttle, surfaced as 429 + Retry-After, and its client-IP resolution is governed by a new TRUSTED_PROXY_HOPS setting (default 0; set 1 behind a single reverse proxy). The dead JWT config (ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES, REFRESH_TOKEN_EXPIRE_DAYS) and the session knobs now owned by the transport (SESSION_COOKIE_MAX_AGE, LOGIN_MAX_ATTEMPTS, LOGIN_WINDOW_MINUTES) are removed; SECRET_KEY goes from dead config to actually used. crudauth supports redis and memory session backends only, so the memcached session backend is gone (the general cache and the non-auth rate limiter still support memcached).
Mobile / JWT
crudauth ships a bearer (JWT) transport that runs alongside sessions for mobile and API clients — both transports resolve to the same Principal, so your route protection doesn't change. It's off by default; see the authentication guide and crudauth's bearer-token guide for the opt-in.
Thanks
The Annotated type-alias DI refactor (#261) landed from co-maintainer @emiliano-go.
If you want to talk to us
Join us on Discord, open an issue, or ping @igorbenav, @LucasQR, @carlosplanchon, or @emiliano-gandini-outeda on the repo. Auth is now a thin wiring layer over crudauth — if there's an auth capability you want (a different transport, an account-recovery flow, a hook), it's likely a crudauth feature you can turn on rather than a fork of the boilerplate.
Full Changelog: v0.18.0...v0.19.0