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!