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.

Browser
App
Proxy
Session Store
OAuth Provider
Email Worker

auth request flow diagram showing browser/app/proxy/session store/OAuth provider/email worker.

Quick Fix / Quick Setup

Run these checks first:

bash
# 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_URL or 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.

bash
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.

bash
curl -i -X POST https://yourapp.com/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"user@example.com","password":"testpass"}'

Look for flags like:

  • HttpOnly
  • Secure
  • SameSite=Lax
  • SameSite=None
  • Domain=...
  • Path=/

Common cookie problems:

  • Secure=true while site is effectively HTTP behind a bad proxy setup
  • wrong cookie domain such as api.example.com when app expects app.example.com
  • SameSite too 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.

bash
redis-cli ping

If 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:

bash
curl -c cookiejar.txt -b cookiejar.txt -i https://yourapp.com/login
curl -c cookiejar.txt -b cookiejar.txt -i https://yourapp.com/protected

If 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.

bash
python - <<'PY'
import jwt
print(jwt.decode('YOUR_TOKEN', options={'verify_signature': False}))
PY

Check:

  • exp
  • iat
  • iss
  • aud
  • 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:

bash
date -u

On 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:

bash
nginx -T | grep -Ei 'proxy_set_header|x-forwarded-proto|host'

For Nginx, you usually need:

nginx
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:

js
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/callback
  • https://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:

python
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_SECURE enabled while serving over HTTP, so browsers drop the cookie
  • cookie domain/path mismatch prevents the browser from sending the session cookie back
  • different SECRET_KEY or JWT secret across app instances invalidates sessions or tokens
  • reverse proxy not forwarding X-Forwarded-Proto causes 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.

bash
printenv | grep -E 'SECRET|SESSION|JWT|COOKIE|CSRF|OAUTH|DOMAIN|EMAIL'
bash
curl -i https://yourapp.com/login
bash
curl -i -X POST https://yourapp.com/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"user@example.com","password":"testpass"}'
bash
curl -c cookiejar.txt -b cookiejar.txt -i https://yourapp.com/protected
bash
python - <<'PY'
import jwt
print(jwt.decode('YOUR_TOKEN', options={'verify_signature': False}))
PY
bash
redis-cli ping
bash
date -u
bash
journalctl -u gunicorn -n 200 --no-pager | grep -Ei 'auth|login|jwt|session|csrf|oauth|cookie'
bash
nginx -T | grep -Ei 'proxy_set_header|x-forwarded-proto|host'
bash
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.

What is the failure type?
Login fails
Check email normalisation, user record existence, and password hash verification
Token fails
Check secret key, expiry, algorithm, and Authorization header format
OAuth callback fails
Check provider callback URL, state parameter, and token exchange config

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

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: