Troubleshooting
Symptom → cause → fix. Grouped by area.
Authentication
| Symptom |
Cause |
Fix |
401 Not authenticated on protected route |
No access token in header/cookie, or expired |
Refresh via POST /auth/refresh (body {"refresh_token":"..."} or refresh cookie). |
401 Invalid email or password |
Wrong password, unknown email, or account locked |
Check token_adapter is set; if account is locked, wait security.lockout_duration seconds. |
401 Account is deactivated |
User's is_active=False |
Reactivate directly in the DB or via your admin tool. |
403 Sign in not allowed |
allow_signin hook returned False |
Check the hook; remember it is only called for OAuth, magic-link, passkey — not /auth/login. |
Cookies / CSRF
| Symptom |
Cause |
Fix |
403 CSRF token missing or invalid on POST/PUT/PATCH/DELETE |
Cookie-mode request missing X-CSRF-Token header matching the csrf_token cookie |
Send the header equal to the readable csrf_token cookie, or set csrf_enabled=False if using Bearer only. |
| Cookies not set after login |
token_delivery is "json" (default) |
Set token_delivery="cookie". |
| Browser discards cookies over HTTP |
Secure=True over plain HTTP |
Use debug=True in dev, or serve over HTTPS. |
Email
| Symptom |
Cause |
Fix |
| No email arrives |
email_transport is None (silent no-op) |
Configure a transport (ConsoleTransport for dev, SMTPTransport for prod). |
MissingDependencyError: 'aiosmtplib' is required... |
email extra not installed |
pip install "sreekarnv-fastauth[email]" |
| Verification email never sent after register |
Registration does not send one. |
Call POST /auth/request-verify-email explicitly. |
Link points at localhost instead of prod URL |
base_url left at default |
Set base_url="https://app.example.com". |
OAuth
| Symptom |
Cause |
Fix |
400 oauth_state_store is not configured |
Missing oauth_state_store |
Add it; use RedisSessionBackend in production. |
400 oauth_adapter is not configured |
Missing oauth_adapter |
Add oauth_adapter=adapter.oauth. |
400 Invalid OAuth state (intermittent) |
Multi-worker + MemorySessionBackend |
Use RedisSessionBackend. |
ProviderError: OAuth redirect_uri is not registered |
redirect_uri not in oauth_allowed_redirect_uris |
Add the exact URL, or set oauth_allowed_redirect_uris=None (not recommended in prod). |
ProviderError: OAuth provider email is not verified |
Existing user, provider returned email_verified=False |
Mark the email verified manually, or sign in via a verified provider. |
400 This provider account is already linked to a user |
Trying to link an OAuth account that belong to another user |
Unlink on that user first via DELETE /auth/oauth/accounts/{provider}. |
RBAC
| Symptom |
Cause |
Fix |
500 RBAC is not configured on require_role/require_permission or /auth/roles |
auth.role_adapter is None |
Set auth.role_adapter = adapter.role. |
Roles from config.roles aren't listed on /auth/roles |
initialize_roles() not called in lifespan |
Add await auth.initialize_roles() inside the lifespan startup. |
| New user gets no default role |
default_role not set, or the user was created before the config had default_role |
Set default_role, or assign manually via /auth/roles/assign. |
403 Insufficient role for admin endpoints |
User is not admin |
Bootstrap the first admin via direct DB write. |
Database
| Symptom |
Cause |
Fix |
500 ... no such table: fastauth_users (or similar) |
create_tables() not called in lifespan |
Add await adapter.create_tables() to the lifespan startup. |
OperationalError with multi-worker SQLite |
SQLite does not support multiple writers |
Use PostgreSQL/MySQL for real apps; SQLite is dev-only. |
JWT / JWKS
| Symptom |
Cause |
Fix |
ConfigError: ... requires jwks_enabled=True |
RS256/RS512 without jwks_enabled |
Set jwt=JWTConfig(algorithm="RS256", jwks_enabled=True). |
jwks_manager is None at first sign-in |
await auth.initialize_jwks() missing |
Call it inside the lifespan startup (after auth.mount(app)). |
/.well-known/jwks.json returns {"keys": []} |
JWKS is disabled or initialized late |
Ensure jwks_enabled=True and initialize_jwks() runs on startup. |
| Tokens invalid after restart |
Auto-generated RSA keys are ephemeral |
Provide your own PEM private_key/public_key. |
InvalidTokenError on resource service after key rotation |
Resource service cached an old KeySet |
Re-fetch /.well-known/jwks.json periodically. |
Do not call initialize_jwks() before auth.mount(app). mount reads static config.jwt.jwks_enabled to register the JWKS route; initialize_jwks populates the JWKS manager at startup (inside the lifespan, which runs after mount). The lifespan order shown in examples/full/main.py is the canonical, correct pattern.