Security Best Practices

← Back to System Design 101 | ← Previous: Observability

Introduction

Security isn't an afterthought—it's a fundamental part of system design. I've learned that security breaches often result from simple oversights, not sophisticated attacks. This article covers security patterns I implement in every production system.

Authentication & Authorization

JWT-Based Authentication

from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

# Configuration
SECRET_KEY = "your-secret-key-here"  # Store in environment variables!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
security = HTTPBearer()

class AuthService:
    """Authentication service."""
    
    def hash_password(self, password: str) -> str:
        """Hash password using bcrypt."""
        return pwd_context.hash(password)
    
    def verify_password(self, plain_password: str, hashed_password: str) -> bool:
        """Verify password against hash."""
        return pwd_context.verify(plain_password, hashed_password)
    
    def create_access_token(self, data: dict, expires_delta: timedelta = None) -> str:
        """Create JWT access token."""
        to_encode = data.copy()
        
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            expire = datetime.utcnow() + timedelta(minutes=15)
        
        to_encode.update({
            "exp": expire,
            "iat": datetime.utcnow(),
            "type": "access"
        })
        
        encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
        return encoded_jwt
    
    def verify_token(self, token: str) -> dict:
        """Verify and decode JWT token."""
        try:
            payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
            return payload
        except JWTError:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Could not validate credentials"
            )

auth_service = AuthService()

# Dependency for protected routes
async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security)
) -> dict:
    """Get current user from JWT token."""
    token = credentials.credentials
    payload = auth_service.verify_token(token)
    
    user_id = payload.get("sub")
    if not user_id:
        raise HTTPException(status_code=401, detail="Invalid token")
    
    user = db.users.find_one({"id": user_id})
    if not user:
        raise HTTPException(status_code=401, detail="User not found")
    
    return user

# Protected endpoint
@app.get("/api/profile")
async def get_profile(current_user: dict = Depends(get_current_user)):
    """Get current user's profile."""
    return current_user

# Login endpoint
@app.post("/api/auth/login")
async def login(email: str, password: str):
    """Authenticate user and return JWT token."""
    user = db.users.find_one({"email": email})
    
    if not user or not auth_service.verify_password(password, user["password_hash"]):
        raise HTTPException(status_code=401, detail="Invalid credentials")
    
    access_token = auth_service.create_access_token(
        data={"sub": user["id"]},
        expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    )
    
    return {
        "access_token": access_token,
        "token_type": "bearer"
    }

Role-Based Access Control (RBAC)

Encryption

Data at Rest

Data in Transit (TLS/SSL)

Input Validation & Sanitization

Rate Limiting & DDoS Protection

Secrets Management

Security Headers

Audit Logging

Lessons Learned

What worked:

  1. JWT for stateless authentication

  2. RBAC for authorization

  3. Encryption for sensitive data

  4. Comprehensive input validation

  5. Rate limiting on all endpoints

  6. Regular security audits

What didn't work:

  1. Storing passwords in plain text (obviously!)

  2. Using "*" for CORS in production

  3. No rate limiting (got DDoSed)

  4. Hardcoded secrets in code

  5. Insufficient audit logging

Security Checklist

  • ✅ Use HTTPS everywhere

  • ✅ Hash passwords with bcrypt/argon2

  • ✅ Validate all user input

  • ✅ Use parameterized queries

  • ✅ Implement rate limiting

  • ✅ Add security headers

  • ✅ Encrypt sensitive data at rest

  • ✅ Use secrets manager

  • ✅ Log security events

  • ✅ Regular security updates

  • ✅ Principle of least privilege

What's Next

With security covered, let's put it all together in a real-world case study:


Navigation:

Last updated