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

MethodUse CaseSecurity LevelManagement
API KeysServer-to-server, simple appsLow (easily leaked)Manual rotation
OAuth 2.0Third-party access, user consentHighToken refresh
JWTStateless authenticationMedium-HighSignature 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

RankVulnerabilityExample
API1Broken Object Level Authorization/api/users/123 accessible by user 456
API2Broken AuthenticationWeak tokens, no rate limiting on login
API3Broken Object Property Level AuthorizationMass assignment allowing role elevation
API4Unrestricted Resource ConsumptionNo pagination limits, memory exhaustion
API5Broken Function Level AuthorizationUser accessing admin endpoints
API6Unrestricted Access to Sensitive Business FlowsAutomated ticket scalping
API7Server Side Request ForgeryFetch arbitrary URLs via API
API8Security MisconfigurationVerbose errors, default credentials
API9Improper Inventory ManagementUndocumented/forgotten API versions
API10Unsafe Consumption of APIsTrusting 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. :::

Quick check: how does this lesson land for you?

Quiz

Module 3: Application Security

Take Quiz
FREE WEEKLY NEWSLETTER

Stay on the Nerd Track

One email per week — courses, deep dives, tools, and AI experiments.

No spam. Unsubscribe anytime.