JBON_DATA

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

  1. Use HTTPS everywhere
  2. Validate all inputs
  3. Implement rate limiting
  4. Use parameterized queries
  5. Log security events
  6. Keep dependencies updated
  7. Implement proper error handling
  8. Regular security audits

Security is not a feature you add at the end. It's a practice you follow from the start.

← Back to Blog