Application Security

API Security

4 min read

APIs are the backbone of modern applications. This lesson covers authentication patterns, common vulnerabilities, and security best practices you'll encounter in interviews.

Authentication Patterns

API Keys vs OAuth 2.0 vs JWT

Method Use Case Security Level Management
API Keys Server-to-server, simple apps Low (easily leaked) Manual rotation
OAuth 2.0 Third-party access, user consent High Token refresh
JWT Stateless authentication Medium-High Signature validation

Interview Question

Q: "When would you use API keys vs OAuth 2.0?"

Answer:

  • API Keys: Internal services, rate limiting identifier, non-sensitive operations
  • OAuth 2.0: Third-party integrations, user data access, delegated authorization
  • JWT: Mobile apps, microservices, when you need stateless auth with claims

OAuth 2.0 Flows

Authorization Code Flow (Most Secure)

┌─────────┐                              ┌──────────────┐
│  User   │                              │  Auth Server │
└────┬────┘                              └──────┬───────┘
     │                                          │
     │  1. Click "Login with Google"            │
     ├────────────────────────────────────────▶│
     │                                          │
     │  2. Redirect to auth server              │
     │◀────────────────────────────────────────┤
     │                                          │
     │  3. User authenticates & consents        │
     ├────────────────────────────────────────▶│
     │                                          │
     │  4. Redirect back with auth code         │
     │◀────────────────────────────────────────┤
     │         │                                │
     │         │  5. Exchange code for tokens   │
     │         └───────────────────────────────▶│
     │                                          │
     │         6. Return access + refresh tokens│
     │◀────────────────────────────────────────┤

PKCE Extension (Required for Public Clients)

import hashlib
import base64
import secrets

# Generate code verifier
code_verifier = secrets.token_urlsafe(32)

# Generate code challenge
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).decode().rstrip('=')

# Use in authorization request
auth_url = f"{AUTH_ENDPOINT}?client_id={CLIENT_ID}&code_challenge={code_challenge}&code_challenge_method=S256"

OWASP API Security Top 10

Rank Vulnerability Example
API1 Broken Object Level Authorization /api/users/123 accessible by user 456
API2 Broken Authentication Weak tokens, no rate limiting on login
API3 Broken Object Property Level Authorization Mass assignment allowing role elevation
API4 Unrestricted Resource Consumption No pagination limits, memory exhaustion
API5 Broken Function Level Authorization User accessing admin endpoints
API6 Unrestricted Access to Sensitive Business Flows Automated ticket scalping
API7 Server Side Request Forgery Fetch arbitrary URLs via API
API8 Security Misconfiguration Verbose errors, default credentials
API9 Improper Inventory Management Undocumented/forgotten API versions
API10 Unsafe Consumption of APIs Trusting third-party API responses

Broken Object Level Authorization (BOLA)

# VULNERABLE - No ownership check
@app.get("/api/orders/{order_id}")
def get_order(order_id: int):
    return db.query(Order).get(order_id)

# SECURE - Verify ownership
@app.get("/api/orders/{order_id}")
def get_order(order_id: int, current_user: User = Depends(get_current_user)):
    order = db.query(Order).get(order_id)
    if order.user_id != current_user.id:
        raise HTTPException(status_code=403, detail="Forbidden")
    return order

Rate Limiting

Implementation Patterns

from fastapi import Request, HTTPException
from datetime import datetime, timedelta
import redis

redis_client = redis.Redis()

def rate_limit(key: str, limit: int, window: int):
    """
    Token bucket rate limiting.

    Args:
        key: Unique identifier (user_id, IP, API key)
        limit: Max requests per window
        window: Time window in seconds
    """
    current = redis_client.get(key)

    if current is None:
        # First request
        redis_client.setex(key, window, 1)
        return True

    if int(current) >= limit:
        raise HTTPException(
            status_code=429,
            detail="Rate limit exceeded",
            headers={"Retry-After": str(window)}
        )

    redis_client.incr(key)
    return True

# Usage
@app.get("/api/search")
def search(request: Request, q: str):
    rate_limit(f"search:{request.client.host}", limit=100, window=60)
    return perform_search(q)

Input Validation

from pydantic import BaseModel, Field, validator
import re

class UserCreateRequest(BaseModel):
    username: str = Field(..., min_length=3, max_length=30)
    email: str = Field(..., max_length=255)
    password: str = Field(..., min_length=12)

    @validator('username')
    def username_alphanumeric(cls, v):
        if not re.match(r'^[a-zA-Z0-9_]+$', v):
            raise ValueError('Username must be alphanumeric')
        return v

    @validator('email')
    def email_valid(cls, v):
        if not re.match(r'^[^@]+@[^@]+\.[^@]+$', v):
            raise ValueError('Invalid email format')
        return v.lower()

    @validator('password')
    def password_strong(cls, v):
        if not re.search(r'[A-Z]', v):
            raise ValueError('Password must contain uppercase')
        if not re.search(r'[a-z]', v):
            raise ValueError('Password must contain lowercase')
        if not re.search(r'\d', v):
            raise ValueError('Password must contain digit')
        return v

Security Headers

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# CORS configuration
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://trusted-domain.com"],
    allow_credentials=True,
    allow_methods=["GET", "POST"],
    allow_headers=["Authorization", "Content-Type"],
)

@app.middleware("http")
async def add_security_headers(request, call_next):
    response = await call_next(request)
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
    response.headers["Content-Security-Policy"] = "default-src 'self'"
    return response

Interview Tip: When discussing API security, always mention the principle of defense in depth: authentication at the gateway, authorization at the service level, and input validation at every endpoint.

Next, we'll cover DevSecOps and CI/CD security. :::

Quiz

Module 3: Application Security

Take Quiz