API Security Checklist for Production Systems
Introduction
Public and internal APIs are high-value attack surfaces. A practical checklist ensures that every release includes the required controls for identity, transport, input validation, and abuse prevention.
Identity and Access Controls
- Enforce OAuth2 or mutual TLS for all production APIs.
- Validate issuer, audience, and signature for every token.
- Use short-lived tokens and enforce refresh token rotation.
- Restrict scopes to the minimum required actions.
Transport and Session Protections
- Require TLS 1.2+ and disable weak cipher suites.
- Use HSTS headers for browser-exposed endpoints.
- Bind session tokens to device or client context when possible.
- Reject requests with missing or duplicate
X-Request-Idvalues.
Input Validation and Data Safety
- Validate schemas at the edge using strict JSON schemas.
- Enforce size limits for headers, bodies, and payload fields.
- Normalize and encode output to prevent injection attacks.
- Perform server-side validation even when clients validate.
Abuse Prevention and Availability
- Apply rate limits per identity, IP, and API key.
- Enforce concurrency limits on expensive operations.
- Introduce circuit breakers for downstream dependencies.
- Use bot detection or behavioral analysis for public traffic.
Monitoring, Auditing, and Response
- Log authentication and authorization decisions with context.
- Emit structured audit logs for privileged actions.
- Alert on repeated access denials or abnormal spikes.
- Document incident playbooks for credential rotation.
Node.js Example: Baseline Express Hardening
The following Node.js example applies strict headers, request validation, and rate limiting.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import express from "express";
import helmet from "helmet";
import rateLimit from "express-rate-limit";
const app = express();
app.use(helmet());
app.use(express.json({ limit: "256kb" }));
app.use(
rateLimit({
windowMs: 60 * 1000,
max: 120,
standardHeaders: true,
legacyHeaders: false,
})
);
app.post("/payments", (req, res) => {
if (!req.headers["x-request-id"]) {
return res.status(400).json({ error: "Missing request id" });
}
return res.status(202).json({ status: "accepted" });
});
Conclusion
A security checklist only works when enforced consistently. Automate these controls with gateways and policy-as-code so every API deployment inherits the same baseline protection.
This post is licensed under CC BY 4.0 by the author.