Secure API Implementation

What Makes an API Secure?

A secure API implements multiple layers: authentication, authorization, rate limiting, input validation, CORS, and logging.

Security Layers

| Layer | Protection | Implementation | |-------|-----------|----------------| | Transport | Encrypts data in transit | TLS / HTTPS only | | Authentication | Verifies identity | JWT tokens | | Rate Limiting | Prevents abuse | Token bucket (10/min) | | Input Validation | Rejects malicious data | Pydantic schemas | | CORS | Controls cross-origin access | Whitelisted origins | | Logging | Creates audit trail | Request/response logging |

Complete Secure API Example

from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer
import jwt, time
from pydantic import BaseModel, EmailStr, field_validator
from slowapi import Limiter
from slowapi.util import get_remote_address
import re

app = FastAPI(title="Secure API")
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter

# CORS — restrict to trusted frontends
ALLOWED_ORIGINS = ["https://your-frontend.com"]
app.add_middleware(
    CORSMiddleware,
    allow_origins=ALLOWED_ORIGINS,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)

# Rate Limiting — prevent brute force and DoS
@app.get("/api/public")
@limiter.limit("10/minute")
async def public_endpoint():
    return {"message": "Public endpoint"}

# Input Validation with Pydantic
class UserCreate(BaseModel):
    email: EmailStr
    name: str
    age: int

    @field_validator('age')
    @classmethod
    def validate_age(cls, v):
        if v < 0 or v > 150:
            raise ValueError('Age must be between 0 and 150')
        return v

    @field_validator('name')
    @classmethod
    def sanitize_name(cls, v):
        return re.sub(r'[<>"\']', '', v)

@app.post("/api/users", status_code=201)
async def create_user(user: UserCreate):
    import psycopg2
    conn = psycopg2.connect("dbname=test")
    cur = conn.cursor()
    cur.execute(
        "INSERT INTO users (email, name, age) VALUES (%s, %s, %s)",
        (user.email, user.name, user.age)
    )
    conn.commit()
    return {"status": "ok"}

# Request Logging
@app.middleware("http")
async def log_requests(request, call_next):
    start = time.time()
    response = await call_next(request)
    elapsed = time.time() - start
    print(f"{request.method} {request.url.path} {response.status_code} {elapsed:.3f}s")
    return response

JWT Authentication

from fastapi.security import HTTPBearer
from jose import JWTError, jwt

SECRET_KEY = "your-jwt-secret"
security = HTTPBearer()

def verify_token(credentials = Depends(security)):
    try:
        payload = jwt.decode(
            credentials.credentials,
            SECRET_KEY,
            algorithms=["HS256"]
        )
        return payload
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.get("/api/protected")
async def protected_endpoint(user = Depends(verify_token)):
    return {
        "message": "Protected data",
        "user_id": user.get("sub")
    }

Defense in Depth Summary

| Layer | What It Stops | |-------|--------------| | HTTPS | Eavesdropping, man-in-the-middle | | JWT Auth | Unauthorized access | | Rate Limiting | Brute force, DoS attacks | | Input Validation | Injection, malformed data | | Parameterized Queries | SQL injection | | CORS | Cross-origin data theft | | Audit Logging | Attack detection and forensics |

Security Headers Checklist

| Header | Value | Purpose | |--------|-------|---------| | Strict-Transport-Security | max-age=63072000 | Enforce HTTPS | | Content-Security-Policy | default-src 'self' | Prevent XSS | | X-Content-Type-Options | nosniff | Prevent MIME sniffing | | X-Frame-Options | DENY | Prevent clickjacking | | Referrer-Policy | strict-origin-when-cross-origin | Control referrer data |

Summary

A secure API combines multiple layers: HTTPS, JWT, rate limiting, input validation, parameterized queries, CORS, and audit logging. Each layer addresses a specific attack vector.

Key takeaways: | HTTPS: mandatory for all production APIs | | JWT: stateless auth with short token expiry | | Rate limiting: 10 requests/minute blocks brute force | | Input validation: reject invalid data before any processing | | Parameterized queries (%s placeholders): prevent SQL injection | | CORS whitelist: only allow trusted origins | | Audit logging: track every request for incident investigation | | Security headers: HSTS, CSP, X-Frame-Options protect the browser side | | All layers together = defense in depth |

You've completed this course! You now understand OWASP Top 10 vulnerabilities and how to build secure APIs.

Threat Modeling for APIs

Common API Threats

| Threat | Description | Mitigation | |--------|-------------|------------| | Broken authentication | Weak or missing auth checks | JWT with validation | | Excessive data exposure | API returns more data than needed | Response schemas, field filtering | | Lack of rate limiting | Unlimited requests exhaust resources | Rate limiter middleware | | Injection | Malicious input executed as code | Parameterized queries, validation | | Mass assignment | Client sets fields it shouldn't | Explicit field allowlist | | Insecure direct object reference | Access other users' data | Ownership check in every endpoint |

API Response Filtering

# Use response_model to control what is returned
from pydantic import BaseModel

class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    # ❌ Don't expose: hashed_password, ssn, internal_notes

@app.get("/api/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    user = get_user_from_db(user_id)
    # Verify the requesting user owns this resource
    if user.id != current_user.id and current_user.role != "admin":
        raise HTTPException(status_code=403, detail="Forbidden")
    return user

Input Validation Checklist

| Check | Why | Example | |-------|-----|---------| | Length limits | Prevent buffer overflow | name: max 100 chars | | Type checking | Prevent type confusion | age: must be integer | | Range validation | Prevent logical errors | age: 0-150 | | Pattern matching | Prevent format injection | email: valid email pattern | | Strip dangerous chars | Prevent XSS | Remove < > " ' | | Allowlist vs denylist | Allowlist is safer | Allowed countries: ["US", "TW", "JP"] |

Summary

Building a secure API requires a comprehensive approach: authentication, authorization, rate limiting, input validation, CORS, logging, and threat modeling. Each layer addresses specific attack vectors.

Key takeaways: | Common API threats: broken auth, excessive data exposure, mass assignment, IDOR | | Response filtering: use response_model to never expose sensitive fields | | Ownership checks: verify the requesting user owns the resource | | Input validation: length, type, range, pattern, character filtering | | Allowlist over denylist: define what's allowed rather than blocking what's not | | Rate limiting: 10 requests/minute prevents brute force | | CORS whitelist: only trusted origins can call your API | | Audit logging: trace every request for security investigations | | Defense in depth: multiple security layers protect against failures in any single layer |

You've completed this course! You now have a complete understanding of web security.

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!