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.