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

SecretRule
SECRET_KEYRandom per environment; rotates if leaked
DATABASE_URLEnvironment only
JWT signing keySame as SECRET_KEY or separate JWT_SECRET

Generate:

bash
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_password in API responses
  • Minimum length policy in Pydantic Field(min_length=8)

JWT Hardening

python
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

python
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

python
app = FastAPI(
    docs_url="/docs" if settings.debug else None,
    redoc_url=None,
    openapi_url="/openapi.json" if settings.debug else None,
)

Never run:

bash
uvicorn app.main:app --reload --host 0.0.0.0

on public servers.

SQL Injection

Use SQLAlchemy ORM or bound parameters:

python
db.scalars(select(User).where(User.username == username))

Never:

python
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):

bash
pip install slowapi
python
from 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 Nginxreverse proxy.

Security Headers

python
@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 response

Add HSTS at Nginx when HTTPS enabled—HTTPS chapter.

Dependency Vulnerabilities

bash
pip audit

Run in CI—Docker and CI chapter.

Authorization Recap

  • 401 — authentication failed
  • 403 — authenticated but forbidden
  • Check resource ownership—permissions chapter

Production Checklist

  • Strong SECRET_KEY from 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 audit in CI
  • /health for 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.