FastAPI Security Checklist for Heroku

FastAPI has built-in security features, but proper configuration is essential for production deployment on Heroku.

Expedited WAF for FastAPI

Expedited WAF adds network level request filtering, preventing malicious attacks from ever touching your application. This enables true defense in depth for your FastAPI app—even if your framework has a vulnerability or is improperly configured, Expedited WAF dynamically blocks threats before they reach your code.

From experience, we’ve seen many applications where attacks technically didn’t succeed, but burned through so many server resources that the application went down anyway. By filtering malicious requests at the network edge, Expedited WAF protects both your security and your availability.

Expedited WAF protects your FastAPI app on Heroku

New Security Features for Your FastAPI App

These capabilities aren’t available out of the box with FastAPI, but Expedited WAF adds them instantly:

FastAPI Core Security

  • Configure CORS properly

    from fastapi.middleware.cors import CORSMiddleware
    
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["https://yourdomain.com"],  # Never use ["*"] in production
        allow_credentials=True,
        allow_methods=["GET", "POST"],
        allow_headers=["Content-Type", "Authorization"],
    )
    
  • Disable docs in production

    app = FastAPI(
        docs_url=None if os.getenv("ENVIRONMENT") == "production" else "/docs",
        redoc_url=None if os.getenv("ENVIRONMENT") == "production" else "/redoc",
    )
    
  • Use Pydantic for validation

    • Leverage Pydantic models for automatic validation
    from pydantic import BaseModel, EmailStr, constr
    
    class User(BaseModel):
        email: EmailStr
        password: constr(min_length=12)
    
  • Implement request validation

    • Use FastAPI’s built-in validation
    • Add custom validators where needed
    • Validate all path, query, and body parameters

Authentication & Authorization

  • Implement OAuth2 with JWT

    from fastapi import Depends, HTTPException, status
    from fastapi.security import OAuth2PasswordBearer
    from jose import JWTError, jwt
    
    oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
    
    async def get_current_user(token: str = Depends(oauth2_scheme)):
        credentials_exception = HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
        )
        # Validate token
        return user
    
  • Use secure password hashing

    from passlib.context import CryptContext
    
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
    
    def hash_password(password: str) -> str:
        return pwd_context.hash(password)
    
  • Implement token expiration

    from datetime import datetime, timedelta
    
    ACCESS_TOKEN_EXPIRE_MINUTES = 30
    
    def create_access_token(data: dict):
        expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
        to_encode = data.copy()
        to_encode.update({"exp": expire})
        return jwt.encode(to_encode, SECRET_KEY, algorithm="HS256")
    
  • Protect sensitive endpoints

    @app.get("/users/me")
    async def read_users_me(current_user: User = Depends(get_current_user)):
        return current_user
    

Database Security

  • Use async database drivers

    from databases import Database
    
    database = Database(DATABASE_URL, ssl="require")
    
  • Always use parameterized queries

    # Good - parameterized
    await database.fetch_one("SELECT * FROM users WHERE email = :email", {"email": email})
    
    # Bad - SQL injection risk
    await database.fetch_one(f"SELECT * FROM users WHERE email = '{email}'")
    
  • Configure Heroku Postgres SSL

    import os
    
    DATABASE_URL = os.getenv("DATABASE_URL", "").replace("postgres://", "postgresql://")
    

API Security

  • Implement rate limiting

    from slowapi import Limiter, _rate_limit_exceeded_handler
    from slowapi.util import get_remote_address
    
    limiter = Limiter(key_func=get_remote_address)
    app.state.limiter = limiter
    
    @app.get("/login")
    @limiter.limit("5/minute")
    async def login(request: Request):
        pass
    
  • Add API versioning

    app = FastAPI()
    
    @app.get("/api/v1/users")
    async def get_users_v1():
        pass
    
  • Implement request/response logging

    • Log all API requests (excluding sensitive data)
    • Monitor for suspicious patterns
    • Use structured logging
  • Set appropriate timeouts

    from fastapi import Request
    import asyncio
    
    @app.middleware("http")
    async def timeout_middleware(request: Request, call_next):
        try:
            return await asyncio.wait_for(call_next(request), timeout=30.0)
        except asyncio.TimeoutError:
            return JSONResponse({"detail": "Request timeout"}, status_code=504)
    

Input Validation & Sanitization

  • Validate file uploads

    from fastapi import File, UploadFile
    
    ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg"}
    MAX_FILE_SIZE = 5 * 1024 * 1024  # 5MB
    
    @app.post("/upload")
    async def upload_file(file: UploadFile = File(...)):
        if not file.filename.split('.')[-1] in ALLOWED_EXTENSIONS:
            raise HTTPException(400, "Invalid file type")
        # Validate size, scan content, etc.
    
  • Sanitize HTML content

    import bleach
    
    def sanitize_html(content: str) -> str:
        return bleach.clean(content, tags=[], strip=True)
    
  • Use FastAPI dependency injection for validation

    • Create reusable validators
    • Share validation logic across endpoints

Security Headers

  • Add security headers middleware
    from fastapi.middleware.trustedhost import TrustedHostMiddleware
    
    app.add_middleware(
        TrustedHostMiddleware,
        allowed_hosts=["yourdomain.com", "*.yourdomain.com"]
    )
    
    @app.middleware("http")
    async def add_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["X-XSS-Protection"] = "1; mode=block"
        response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
        return response
    

Environment & Configuration

  • Use Pydantic Settings

    from pydantic import BaseSettings
    
    class Settings(BaseSettings):
        database_url: str
        secret_key: str
        debug: bool = False
    
        class Config:
            env_file = ".env"
    
    settings = Settings()
    
  • Store secrets in Heroku config

    heroku config:set SECRET_KEY='...'
    heroku config:set DATABASE_URL='...'
    
  • Never expose secrets in responses

    • Use Pydantic response models
    • Exclude sensitive fields

Heroku Deployment

  • Use Uvicorn with proper settings

    # Procfile
    web: uvicorn main:app --host 0.0.0.0 --port $PORT --workers 4
    
  • Configure Python runtime

    # runtime.txt
    python-3.11.7
    
  • Pin dependencies

    pip freeze > requirements.txt
    

Async Security Considerations

  • Prevent async resource exhaustion

    • Limit concurrent tasks
    • Implement proper backpressure
    • Use connection pooling
  • Secure WebSocket connections

    from fastapi import WebSocket
    
    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket, token: str = Depends(get_current_user)):
        await websocket.accept()
        # Handle WebSocket communication
    

Testing

  • Write security tests

    from fastapi.testclient import TestClient
    
    def test_unauthorized_access():
        response = client.get("/users/me")
        assert response.status_code == 401
    
  • Test on Heroku staging

    • Create staging app
    • Test all security configurations
    • Load test API endpoints

Dependencies & Monitoring

  • Scan dependencies

    pip install pip-audit
    pip-audit
    
  • Monitor API performance

    • Track response times
    • Monitor error rates
    • Set up alerts
  • Implement health checks

    @app.get("/health")
    async def health_check():
        return {"status": "healthy"}
    

Additional Resources