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.