Post

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-Id values.

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.