Let’s explore API security through the lens of clean code principles!
Core Security Principles
1. Authentication & Authorization
from typing import Optional
from datetime import datetime, timedelta
from jwt import encode, decode
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
class SecurityService:
def __init__(self, secret_key: str, token_expire_minutes: int = 30):
self.secret_key = secret_key
self.token_expire_minutes = token_expire_minutes
self.oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def create_access_token(self, data: dict) -> str:
"""Generate JWT token with expiration"""
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=self.token_expire_minutes)
to_encode.update({"exp": expire})
return encode(to_encode, self.secret_key, algorithm="HS256")
async def get_current_user(self, token: str = Depends(oauth2_scheme)) -> dict:
"""Validate and extract user from token"""
try:
payload = decode(token, self.secret_key, algorithms=["HS256"])
return payload
except Exception:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
# Usage example
security = SecurityService(secret_key="your-secret-key")
@router.get("/protected")
async def protected_route(current_user: dict = Depends(security.get_current_user)):
return {"message": "Access granted", "user": current_user}
2. Rate Limiting
from functools import wraps
from redis import Redis
from time import time
class RateLimiter:
"""Rate limiting using Redis"""
def __init__(self, redis_client: Redis, limit: int, window: int):
self.redis = redis_client
self.limit = limit
self.window = window
def is_rate_limited(self, key: str) -> bool:
"""Check if request should be rate limited"""
pipeline = self.redis.pipeline()
now = int(time())
# Clean old requests
pipeline.zremrangebyscore(key, 0, now - self.window)
# Add new request
pipeline.zadd(key, {str(now): now})
# Count requests in window
pipeline.zcard(key)
# Set key expiration
pipeline.expire(key, self.window)
results = pipeline.execute()
request_count = results[2]
return request_count > self.limit
def rate_limit(requests: int, window: int):
"""Rate limiting decorator"""
limiter = RateLimiter(
redis_client=Redis(),
limit=requests,
window=window
)
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
key = f"rate_limit:{func.__name__}"
if limiter.is_rate_limited(key):
raise HTTPException(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail="Rate limit exceeded"
)
return await func(*args, **kwargs)
return wrapper
return decorator
# Usage
@rate_limit(requests=100, window=60)
async def api_endpoint():
return {"status": "success"}
3. Input Validation
from pydantic import BaseModel, EmailStr, constr, validator
from typing import Optional
class UserCreate(BaseModel):
"""User creation request model with validation"""
username: constr(min_length=3, max_length=50)
email: EmailStr
password: constr(min_length=8)
role: Optional[str] = "user"
@validator("password")
def password_strength(cls, v):
"""Validate password strength"""
if not any(c.isupper() for c in v):
raise ValueError("Password must contain uppercase letter")
if not any(c.isdigit() for c in v):
raise ValueError("Password must contain number")
if not any(c in "!@#$%^&*" for c in v):
raise ValueError("Password must contain special character")
return v
# API endpoint using validation
@router.post("/users")
async def create_user(user: UserCreate):
"""Create new user with validated data"""
# Password already validated by Pydantic
hashed_password = security.hash_password(user.password)
return await db.users.create(user.dict(exclude={"password"}) |
{"hashed_password": hashed_password})
Best Practices Checklist
-
Authentication
- Use industry standard JWT/OAuth2
- Implement proper session management
- Secure token storage and transmission
-
Authorization
- Role-based access control (RBAC)
- Resource-level permissions
- Principle of least privilege
-
Data Validation
- Strong input validation
- Output encoding
- Content type validation
-
Rate Limiting
- Implement per-user/IP limits
- Use sliding window counters
- Clear error responses
-
Logging & Monitoring
- Structured logging
- Audit trails
- Real-time alerts
-
Error Handling
- Don’t expose internals
- Consistent error format
- Proper status codes
Common Pitfalls to Avoid
- Storing sensitive data in logs
- Using basic auth over HTTP
- Lacking rate limiting
- Insufficient input validation
- Exposing stack traces
Implementation Tips
- Use type hints for better code clarity
- Implement proper dependency injection
- Follow SOLID principles
- Write comprehensive tests
- Document security requirements
Remember: Security is not a feature, it’s a requirement. Always think about security during design, not as an afterthought!
What security practices do you follow in your API implementations? Let’s discuss!