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.
New Security Features for Your FastAPI App
These capabilities aren’t available out of the box with FastAPI, but Expedited WAF adds them instantly:
- Block IP Addresses - Stop malicious actors by IP or CIDR range
- Block User Agents - Filter out bad bots and scrapers
- Block by Geolocation - Restrict traffic by country or region
- DDoS Protection - Stop attacks with CAPTCHA challenges
- Block Anonymous Proxies - Prevent traffic from VPNs and proxy networks
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"}