FastAPI Security Best Practices
Introduction
FastAPI gives validation and docs by default, but security still depends on configuration—secrets, CORS, JWT handling, SQL access, and production hardening. This chapter collects the essential checklist before exposing an API on the public internet.
Prerequisites
Secrets and Environment
| Secret | Rule |
|---|---|
SECRET_KEY | Random per environment; rotates if leaked |
DATABASE_URL | Environment only |
| JWT signing key | Same as SECRET_KEY or separate JWT_SECRET |
Generate:
python -c "import secrets; print(secrets.token_hex(32))"Never commit .env—see Git .gitignore.
Warning
No Secrets in Git History
Removing a leaked key requires rotation—.gitignore does not fix past commits.
Password Storage
Use bcrypt via passlib—see authentication chapter:
- Never log passwords
- Never return
hashed_passwordin API responses - Minimum length policy in Pydantic
Field(min_length=8)
JWT Hardening
ACCESS_TOKEN_EXPIRE_MINUTES = 30
ALGORITHM = "HS256"- Prefer short-lived access tokens
- Use HTTPS only in production
- Do not put secrets in JWT payload
- RS256 when multiple services verify tokens without shared secret
Revocation requires blocklist or short TTL—Flask JWT notes.
CORS
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)- No
allow_origins=["*"]with credentials - Tighten origins per environment
Disable Dev Features in Production
app = FastAPI(
docs_url="/docs" if settings.debug else None,
redoc_url=None,
openapi_url="/openapi.json" if settings.debug else None,
)Never run:
uvicorn app.main:app --reload --host 0.0.0.0on public servers.
SQL Injection
Use SQLAlchemy ORM or bound parameters:
db.scalars(select(User).where(User.username == username))Never:
db.execute(text(f"SELECT * FROM users WHERE name = '{name}'"))Input Validation
Pydantic catches type errors—also validate business rules:
- File upload size and MIME type—uploads chapter
- Pagination caps:
limit: int = Query(20, le=100)
Rate Limiting
slowapi (concept):
pip install slowapifrom fastapi import Request
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@app.get("/public")
@limiter.limit("60/minute")
def public(request: Request):
return {"ok": True}Or limit at Nginx—reverse proxy.
Security Headers
@app.middleware("http")
async def security_headers(request: Request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
return responseAdd HSTS at Nginx when HTTPS enabled—HTTPS chapter.
Dependency Vulnerabilities
pip auditRun in CI—Docker and CI chapter.
Authorization Recap
- 401 — authentication failed
- 403 — authenticated but forbidden
- Check resource ownership—permissions chapter
Production Checklist
- Strong
SECRET_KEYfrom environment -
DEBUG=false, no--reload - HTTPS terminated at Nginx or load balancer
- CORS origins explicit
- Docs disabled or protected
- bcrypt passwords, no secrets in responses
- ORM only—no raw SQL string concat
- Rate limits on login and public APIs
-
pip auditin CI -
/healthfor monitoring without sensitive data
Compare Flask security.
FAQ
FastAPI secure by default?
Validation helps; misconfiguration (open CORS, weak JWT secret) causes incidents.
OAuth2 third-party login?
Use Authlib or dedicated IdP—beyond password flow in this track.
CSP for API?
Mostly for HTML error pages; JSON APIs rely on auth + HTTPS.
Log JWT tokens?
Never—treat as passwords.
pentest before launch?
Recommended for PII and payment APIs.
SameSite cookies?
Relevant if adding cookie sessions alongside JWT.