Quickstart¶
A complete, runnable credentials app in five minutes. We use SQLite (ships with standard) and JSON token delivery.
1. Install¶
2. Create main.py¶
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI
from fastauth import FastAuth, FastAuthConfig
from fastauth.adapters.sqlalchemy import SQLAlchemyAdapter
from fastauth.api.deps import require_auth
from fastauth.providers.credentials import CredentialsProvider
from fastauth.types import UserData
adapter = SQLAlchemyAdapter(engine_url="sqlite+aiosqlite:///./auth.db")
# Generate a real secret with `fastauth generate-secret` and paste it here.
SECRET = "change-me-in-production-min-32-bytes!!"
auth = FastAuth(FastAuthConfig(
secret=SECRET,
providers=[CredentialsProvider()],
adapter=adapter.user,
token_adapter=adapter.token,
))
@asynccontextmanager
async def lifespan(app: FastAPI):
await adapter.create_tables()
yield
app = FastAPI(lifespan=lifespan)
auth.mount(app)
@app.get("/dashboard")
async def dashboard(user: UserData = Depends(require_auth)):
return {"hello": user["email"], "id": user["id"]}
3. Run¶
adapter.create_tables() runs on startup and creates the fastauth_* tables (CREATE IF NOT EXISTS).
4. Register and log in¶
# Register
curl -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"a@b.com","password":"s3cur3-pw!","name":"A"}'
# -> 201 {"access_token": "...", "refresh_token": "...", "token_type": "bearer", "expires_in": 900}
# Log in
RESP=$(curl -s -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"a@b.com","password":"s3cur3-pw!"}')
ACCESS=$(python -c "import json; print(json.loads('''$RESP''')['access_token'])")
# hit a protected route
curl http://localhost:8000/dashboard -H "Authorization: Bearer $ACCESS"
# -> {"hello":"a@b.com","id":"..."}
5. What just happened¶
FastAuthConfigis the configuration object; the three required fields aresecret,providers, andadapter.adapter.useris aUserAdapter;adapter.tokenis aTokenAdapterused to persist one-time verification/reset tokens and to enforce refresh-token revocation/replay protection.require_token_adapter_for_refreshdefaults toTrue, sotoken_adapteris required to issue refresh tokens.auth.mount(app)includes the/auth/*router at the configuredroute_prefix(default/auth).require_authis a FastAPI dependency that reads the access token fromAuthorization: Beareror theaccess_tokencookie, decodes it, and returns theUserDatarecord. It raises401if absent/expired or the account is inactive.
Where to go next¶
- Configuration — every field and default.
- Protecting routes —
require_role,require_permission. - Cookies — switch to cookie delivery.
- Examples — OAuth, passkeys, magic links, JWKS microservice.
Common mistakes¶
ConfigError: 'secret' must be at least 32 bytes— HS256 (the default) requires a ≥32-byte secret. Runfastauth generate-secret.ConfigError: token_adapter is required when require_token_adapter_for_refresh=True— you must passtoken_adapter=adapter.token(or setrequire_token_adapter_for_refresh=Falseonly for tests/demos).- Forgetting
await adapter.create_tables()in the lifespan — endpoints then return 500 because tables don't exist. 401 Not authenticatedon/dashboard— you sent the refresh token instead of the access token, or the JWT is expired. Refresh withPOST /auth/refresh(body{"refresh_token": "..."}).