Secrets Management and Key Rotation
Load secrets from vaults, rotate keys safely, and avoid leaking credentials in logs or images.
Why Secrets Need Special Handling
A secret is any value that grants access: database passwords, API keys, signing keys, OAuth client secrets. In a FastAPI backend these leak far more often than people expect.
- Hardcoded in source and pushed to Git history forever
- Printed into logs during debugging
- Baked into Docker image layers
- Echoed back in error responses or
/debugendpoints
The discipline in this lesson: load secrets from a trusted source at runtime, never persist them where humans or images can read them, and rotate them on a schedule so a leak has a short blast radius.
Step 1 — Pull Config From the Environment
The baseline for any production FastAPI app is loading secrets from the environment, not from code. Pydantic's BaseSettings reads env vars (and optionally a local .env for dev) and validates them at startup.
If a required secret is missing the app fails fast on boot instead of crashing on the first request. Notice we type SecretStr so the value is masked if the object is ever printed.
from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file='.env', extra='ignore')
database_url: SecretStr
jwt_signing_key: SecretStr
stripe_api_key: SecretStr
settings = Settings()
# Printing the model never reveals the raw value:
print(settings.jwt_signing_key) # secret='**********'
print(settings.jwt_signing_key.get_secret_value()[:0]) # access only when neededAll lessons in this course
- Mitigating the OWASP API Security Top 10
- Rate Limiting and Bot Abuse Protection
- Secrets Management and Key Rotation
- CORS, CSP and Secure Header Policies