API Security Best Practices
APIs expose your systems to the world. Securing them properly isn't optional—it's essential for protecting your data and your customers.
Authentication
JWT Best Practices
import jwt
from datetime import datetime, timedelta
def create_token(user_id: str, secret: str) -> str:
payload = {
'sub': user_id,
'iat': datetime.utcnow(),
'exp': datetime.utcnow() + timedelta(hours=1),
'jti': str(uuid.uuid4()) # Unique token ID
}
return jwt.encode(payload, secret, algorithm='HS256')
def verify_token(token: str, secret: str) -> dict:
try:
return jwt.decode(token, secret, algorithms=['HS256'])
except jwt.ExpiredSignatureError:
raise AuthenticationError("Token expired")
except jwt.InvalidTokenError:
raise AuthenticationError("Invalid token")
OAuth 2.0 Flows
- Authorization Code: For server-side apps
- PKCE: For mobile and SPAs
- Client Credentials: For machine-to-machine
Authorization
from functools import wraps
def require_permission(permission: str):
"""Decorator for permission-based access control."""
def decorator(f):
@wraps(f)
def decorated(*args, **kwargs):
user = get_current_user()
if not user.has_permission(permission):
raise PermissionDenied(f"Requires {permission}")
return f(*args, **kwargs)
return decorated
return decorator
@app.route('/api/v1/admin/users')
@require_permission('admin:read')
def list_users():
return get_all_users()
Input Validation
from pydantic import BaseModel, validator, constr
class CreateOrderRequest(BaseModel):
product_id: constr(regex='^[A-Z0-9]{8}$')
quantity: int
@validator('quantity')
def validate_quantity(cls, v):
if v < 1 or v > 1000:
raise ValueError('Quantity must be between 1 and 1000')
return v
Rate Limiting
from flask_limiter import Limiter
limiter = Limiter(
key_func=get_remote_address,
default_limits=["100 per minute", "1000 per hour"]
)
@app.route('/api/v1/search')
@limiter.limit("10 per second")
def search():
return perform_search(request.args)
Security Headers
@app.after_request
def add_security_headers(response):
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
Logging and Monitoring
import logging
def log_api_request(request, response, duration):
log_data = {
'timestamp': datetime.utcnow().isoformat(),
'method': request.method,
'path': request.path,
'status': response.status_code,
'duration_ms': duration * 1000,
'user_id': get_user_id(),
'ip': request.remote_addr
}
# Redact sensitive data
log_data['params'] = redact_sensitive(request.args)
logging.info('API Request', extra=log_data)
Security Checklist
- Use HTTPS everywhere
- Validate all inputs
- Implement rate limiting
- Use parameterized queries
- Log security events
- Keep dependencies updated
- Implement proper error handling
- Regular security audits
Security is not a feature you add at the end. It's a practice you follow from the start.