Authentication (API Keys, OAuth, JWT)
Authentication verifies *who* is making the request.
- [ ] Use HTTPS exclusively — never transmit credentials over HTTP
- [ ] API Keys: treat as passwords — store hashed (SHA-256), never log raw keys, use 32+ bytes of entropy
- [ ] JWT tokens: validate signature (
alg,iss,aud,exp). Rejectalg: noneattacks explicitly - [ ] OAuth 2.0: use PKCE for public clients, short-lived access tokens (< 1 hour), refresh token rotation
- [ ] Default credentials: remove all default/test credentials before production deployment
# JWT validation — always specify allowed algorithms
import jwt
payload = jwt.decode(
token,
key=PUBLIC_KEY,
algorithms=['RS256'], # never allow 'none'
audience='api.example.com',
issuer='auth.example.com',
)
Authorization
Authorization verifies *what* the authenticated identity may do.
- [ ] Enforce authorization on every endpoint — never rely on the UI hiding buttons
- [ ] Object-level authorization (IDOR): verify the authenticated user owns or has permission to access each object by ID
- [ ] Function-level authorization: block lower-privilege users from calling admin endpoints
- [ ] Principle of least privilege: scopes should grant only the minimum necessary access
IDOR (Insecure Direct Object Reference) is the #1 API vulnerability: an attacker changes GET /orders/42 to GET /orders/43 and gets someone else's order. Always filter by user_id = current_user.id.
Rate Limiting
- [ ] Rate limit all endpoints, especially authentication and password reset
- [ ] Limit by user ID as well as IP (authenticated users can change IPs)
- [ ] Return
429 Too Many RequestswithRetry-Afterheader - [ ] Implement exponential backoff for login failures (account lockout)
Input Validation
- [ ] Validate all input against a strict schema — reject unknown fields
- [ ] Enforce maximum sizes: string length, array size, numeric ranges
- [ ] Validate content types — reject requests with unexpected
Content-Type - [ ] Sanitize data used in database queries — use parameterized queries
- [ ] Never trust client-supplied IDs for ownership — re-verify server-side
HTTPS Everywhere
- [ ] Redirect HTTP to HTTPS (301 or HSTS preload)
- [ ] Set
Strict-Transport-Security: max-age=31536000; includeSubDomains - [ ] Use TLS 1.2+ only — disable TLS 1.0 and 1.1
- [ ] Use strong cipher suites — disable RC4, 3DES, NULL ciphers
CORS Policy
- [ ] Whitelist specific allowed origins — avoid
*for authenticated APIs - [ ] Do not allow arbitrary origins by echoing
Originwithout validation - [ ] Use
Access-Control-Allow-Credentials: trueonly when necessary
Error Message Safety
- [ ] Do not expose stack traces, internal paths, or database errors to clients
- [ ] Use generic error messages for authentication failures (
Invalid credentials, notUser not found) - [ ] Return RFC 9457 Problem Details format for structured errors
- [ ] Log detailed errors server-side; surface only safe summaries to clients
Logging and Monitoring
- [ ] Log all authentication events (success and failure) with IP and user agent
- [ ] Alert on abnormal patterns: unusual request rates, credential stuffing patterns, mass enumeration
- [ ] Never log credentials, tokens, or PII — mask in logs before writing
- [ ] Correlate logs with a request ID (
X-Request-ID) for incident investigation
Security Headers
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()
Dependency Security
- [ ] Run
pip audit/npm auditregularly and in CI - [ ] Pin dependency versions in production
- [ ] Subscribe to security advisories for critical dependencies
- [ ] Remove unused dependencies — each dependency is an attack surface