The default FastAPI security posture
FastAPI is genuinely excellent at making you fast. That's also the problem. The framework guides you toward working endpoints, not secure ones. A fresh FastAPI app has no auth, no rate limiting, no CORS policy, and Pydantic validation that only checks types — not values.
None of this is the framework's fault. Security is application-specific. But it means every FastAPI backend needs deliberate hardening before exposure, and the four layers below cover the vast majority of the attack surface.
Layer 1: signed sessions with JWT
FastAPI has no built-in session management. The standard approach is python-jose for JWT creation and passlib for password hashing. The key decision is what goes in the token payload.
The most common mistake is treating JWTs as opaque. They're not — base64-decode the payload and you'll see everything in plain text. Keep the payload minimal: a user ID, the expiry, and nothing else. Role checks happen server-side on each request by looking up the user ID.
Layer 2: SlowAPI rate limiting
SlowAPI is a thin FastAPI/Starlette wrapper around the limits library. Adding it takes about 10 lines. The two configurations that matter in practice: a global limit to prevent bulk scraping, and a tighter limit on auth endpoints specifically to stop credential stuffing.
One gotcha: if your app sits behind a reverse proxy, get_remote_address will rate-limit the proxy's IP, not the client. Fix this by using request.headers.get("X-Forwarded-For") as the key function and trusting only known proxy IPs.
Layer 3: Pydantic strict validation
Pydantic validates types by default. It does not validate values. An email field that passes str validation can still accept "'; DROP TABLE users; --". Add validators for anything that touches a database or gets reflected back to users.
Layer 4: default-deny auth middleware
Rather than decorating every endpoint with an auth dependency, I add a middleware that denies all requests unless the path is on an explicit allowlist. This catches the "I forgot to add auth to that endpoint" failure mode at the architecture level.
The 5-minute pre-staging checklist
JWT with expiry < 60 min
Rate limiting on auth endpoints
Pydantic validators on all user input
Default-deny middleware
CORS locked to known origins
Debug mode left on in prod
SECRET_KEY hardcoded in source
/docs exposed in production
Password in JWT payload
X-Forwarded-For not handled
The common misses column is a checklist, not a judgment. All of them have shipped to production at some point. The most damaging is the secret key in source — once it's in git history, rotating it requires invalidating every existing session, which becomes an incident if you have active users.