Common Auth Bugs and Fixes
The essential playbook for implementing common auth bugs and fixes in your SaaS.
This troubleshooting playbook helps you isolate and fix broken authentication in Flask, FastAPI, and similar SaaS stacks. Use it to determine whether the failure is caused by cookies, sessions, JWT validation, CSRF, OAuth redirects, password hashing, email flows, or local vs production environment mismatch.
auth request flow diagram showing browser/app/proxy/session store/OAuth provider/email worker.
Quick Fix / Quick Setup
Run these checks first:
# 1) Verify env values used by auth
printenv | grep -E 'SECRET|SESSION|JWT|COOKIE|CSRF|OAUTH|DOMAIN|EMAIL'
# 2) Inspect auth cookies and headers
curl -i https://yourapp.com/login
curl -i -X POST https://yourapp.com/login \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","password":"testpass"}'
# 3) Check if Set-Cookie is actually returned
curl -I https://yourapp.com/login
# 4) Validate app can reach DB/session store
python -c "from app import db; print('db ok')"
redis-cli ping
# 5) Check reverse proxy forwards scheme/host correctly
curl -I https://yourapp.com
# 6) Review auth-related errors in logs
journalctl -u gunicorn -n 200 --no-pager | grep -Ei 'auth|login|jwt|session|csrf|oauth|cookie'
docker logs <container_name> --tail 200 | grep -Ei 'auth|login|jwt|session|csrf|oauth|cookie'Most production auth bugs come from:
- bad cookie settings
- wrong
APP_URLor OAuth redirect URIs - mismatched secrets
- expired tokens
- proxy HTTPS misconfiguration
- session storage not shared across instances
What’s happening
Authentication failures usually happen because one part of the auth chain disagrees with another:
- browser
- reverse proxy
- app server
- database
- cache/session store
- email provider
- OAuth provider
Typical failure patterns:
- login succeeds in app code, but browser does not store the cookie
- JWT exists, but backend rejects it due to secret, issuer, audience, or expiry mismatch
- OAuth callback returns to the app, but state or callback URL validation fails
- password reset link is generated with the wrong domain or signed with a changed secret
- users authenticate on one instance and fail on another due to inconsistent env values
Step-by-step implementation
1. Identify the exact failing auth path
Do not debug all auth at once. Reproduce one path only:
- login form
- registration
- logout
- OAuth login
- password reset
- email verification
- protected API request
- admin-only route
Determine whether the issue affects:
- browser session auth
- bearer/JWT auth
- both
2. Verify runtime environment values
Check the env used by the running process, not just .env.
printenv | grep -E 'SECRET|SESSION|JWT|COOKIE|CSRF|OAUTH|DOMAIN|EMAIL'Look for:
SECRET_KEY- JWT signing secret
- session secret
- cookie domain
- app/public base URL
- OAuth client ID/secret
- OAuth callback URL
- email sender/base URL
If you run multiple replicas, compare values across instances.
3. Check cookies
Inspect whether the login response actually sends Set-Cookie.
curl -i -X POST https://yourapp.com/login \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","password":"testpass"}'Look for flags like:
HttpOnlySecureSameSite=LaxSameSite=NoneDomain=...Path=/
Common cookie problems:
Secure=truewhile site is effectively HTTP behind a bad proxy setup- wrong cookie domain such as
api.example.comwhen app expectsapp.example.com SameSitetoo strict for OAuth or cross-subdomain flows- logout clearing a different cookie than the one originally set
4. Test session persistence
If using server-side sessions, verify the session store works.
redis-cli pingIf sessions are stored in memory, they may fail after:
- restart
- deploy
- scaling to multiple instances
Use shared storage such as Redis or database-backed sessions if requests can hit different instances.
Test with curl cookie jar:
curl -c cookiejar.txt -b cookiejar.txt -i https://yourapp.com/login
curl -c cookiejar.txt -b cookiejar.txt -i https://yourapp.com/protectedIf the second request is unauthorized, either:
- the cookie was not stored
- the cookie was not sent back
- the session store lost the session
- auth middleware is not reading it correctly
5. Validate JWT content
If using JWT auth, decode a token and inspect claims before changing code.
python - <<'PY'
import jwt
print(jwt.decode('YOUR_TOKEN', options={'verify_signature': False}))
PYCheck:
expiatissaud- subject/user ID
- algorithm
Typical JWT failures:
- wrong signing key
- HS256 vs RS256 mismatch
- expired token
- incorrect issuer or audience
- clock skew on server nodes
Check server time:
date -uOn multiple nodes, compare all of them.
6. Verify reverse proxy headers
Apps commonly mis-detect HTTPS when behind Nginx, Caddy, Traefik, or a load balancer.
Inspect proxy config:
nginx -T | grep -Ei 'proxy_set_header|x-forwarded-proto|host'For Nginx, you usually need:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;If this is wrong, the app may:
- generate HTTP callback URLs
- set cookies incorrectly
- fail CSRF origin checks
- break OAuth redirect validation
7. Check CSRF and CORS behavior
For form-based auth:
- ensure CSRF token is generated
- ensure hidden token is submitted
- ensure session is stable
- ensure trusted origins are correct
For SPA + API auth:
- allow the exact frontend origin
- enable credentials only where needed
- confirm frontend sends cookies with
credentials: 'include'
Example frontend fetch:
fetch("https://api.example.com/login", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password })
})Common errors:
- browser blocks cookie on cross-site request
- CORS allows
*but credentials are enabled - CSRF token tied to stale session
- frontend sends request without cookies
8. Validate OAuth settings exactly
OAuth failures are usually configuration failures, not code failures.
Check that redirect URI matches exactly:
- scheme
- subdomain
- path
- trailing slash
Examples:
https://app.example.com/auth/google/callbackhttps://app.example.com/auth/google/callback/
These are often treated as different URIs.
Also verify:
- state parameter generated and stored correctly
- state survives through session/cookie handling
- callback is processed by the same environment that initiated login
9. Check password hashing compatibility
If old users cannot log in but new ones can, check hashing config.
Typical causes:
- changed hashing algorithm
- changed passlib/bcrypt settings
- wrong verification logic after migration
- stored hash truncated or malformed
Minimal verification example with passlib:
from passlib.context import CryptContext
pwd = CryptContext(schemes=["bcrypt"], deprecated="auto")
print(pwd.verify("testpass", "$2b$12$examplehashgoeshere"))10. Inspect password reset and verification flows
Check:
- generated link domain
- token expiry
- token signing secret
- URL encoding
- background worker execution
- email provider delivery logs
If your app generates links with localhost or internal container hostnames, reset links will fail in production.
11. Verify backend authorization checks
Do not assume login problems are auth-only. Some failures are authorization bugs.
Examples:
- route middleware not attached
- JWT validates but role check missing
- frontend hides admin UI, backend still allows access
- stale role claims remain in JWT after permission changes
Review protected routes against your implementation in Protecting Routes and APIs.
12. Retest the full flow end-to-end
After any fix:
- open an incognito window
- repeat the exact flow
- test with browser and
curl - verify logs
- test one normal user and one admin user
- test local, staging, and production if applicable
Common causes
SESSION_COOKIE_SECUREenabled while serving over HTTP, so browsers drop the cookie- cookie domain/path mismatch prevents the browser from sending the session cookie back
- different
SECRET_KEYor JWT secret across app instances invalidates sessions or tokens - reverse proxy not forwarding
X-Forwarded-Protocauses incorrect HTTPS detection and callback URLs - CORS configuration blocks credentialed requests between frontend and API subdomains
- CSRF token missing, stale, or not bound to the active session
- JWT expired, signed with the wrong algorithm, or validated with wrong issuer/audience
- OAuth redirect URI does not exactly match provider configuration
- password hashes generated with one algorithm/settings but verified with incompatible configuration
- session storage uses in-memory state and breaks after restart or multi-instance deployment
- logout clears a cookie with the wrong domain/path so the browser keeps the original cookie
- reset or verification links use localhost or the wrong public base URL in production
Debugging tips
Use these commands directly.
printenv | grep -E 'SECRET|SESSION|JWT|COOKIE|CSRF|OAUTH|DOMAIN|EMAIL'curl -i https://yourapp.com/logincurl -i -X POST https://yourapp.com/login \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","password":"testpass"}'curl -c cookiejar.txt -b cookiejar.txt -i https://yourapp.com/protectedpython - <<'PY'
import jwt
print(jwt.decode('YOUR_TOKEN', options={'verify_signature': False}))
PYredis-cli pingdate -ujournalctl -u gunicorn -n 200 --no-pager | grep -Ei 'auth|login|jwt|session|csrf|oauth|cookie'nginx -T | grep -Ei 'proxy_set_header|x-forwarded-proto|host'docker logs <container_name> --tail 200 | grep -Ei 'auth|login|jwt|session|csrf|oauth|cookie'Additional debugging rules:
- inspect browser DevTools Network tab for
Set-Cookie,Cookie,Origin,Location, and CORS headers - search logs by user email or request ID
- reduce auth flow to a minimal endpoint that sets and reads one session value
- compare release versions and env vars across all app replicas
- inspect email provider logs for reset and verification flows
decision tree for login fails vs token fails vs OAuth callback fails.
Checklist
- ✓ secrets are present and consistent across all app instances
- ✓ cookie flags match deployment setup and HTTPS usage
- ✓ session store is reachable and persistent
- ✓ JWT claims and signing config are valid
- ✓ proxy forwards scheme and host correctly
- ✓ CORS and CSRF settings match frontend architecture
- ✓ OAuth redirect URIs match provider configuration exactly
- ✓ password reset and email verification links use the correct public domain
- ✓ RBAC checks run on backend routes and APIs
- ✓ logout clears the correct cookie or invalidates the token as designed
- ✓ auth flows have been tested in an incognito browser session and with
curl - ✓ logs and error tracking capture auth failures with enough context to reproduce
For broader launch verification, use SaaS Production Checklist.
Related guides
- Implement User Authentication (Login/Register)
- Session Management vs JWT (When to Use What)
- Protecting Routes and APIs
- SaaS Production Checklist
FAQ
Why does my app log users in and then immediately log them out?
The session cookie is usually not being stored or returned. Check Secure, SameSite, domain, path, proxy HTTPS detection, and whether the session backend persists data.
Why is OAuth login failing after redirecting back from Google or GitHub?
Most often the callback URL is mismatched, state validation fails, or cookies required for the flow are blocked due to SameSite or cross-site settings.
Why do protected API routes return 401 even with a token?
Check the token signature, expiry, issuer, audience, algorithm, and whether the backend is reading the token from the expected header or cookie.
Why do auth bugs appear only after scaling to multiple instances?
You may have inconsistent secrets across instances or session storage tied to process memory instead of shared Redis or database storage.
What should I check first for password reset problems?
Check token expiry, signing secret stability, generated reset URL domain, URL encoding, and whether the email was actually sent by the background worker or provider.
Final takeaway
Treat auth bugs as a chain problem: browser, proxy, app, storage, and provider settings must all agree.
Start with these first:
- cookies
- secrets
- domain and HTTPS settings
- session persistence
Then move to:
- JWT claims
- CSRF/CORS
- OAuth redirect validation
- password reset/email flows
After the fix, document one canonical setup and keep it aligned with: