Kodokon kodokon.com

Securing an API in production

Harden a Node API in production: correct CORS, rate limiting, security headers, and defenses against the main OWASP API risks.

11 min · 3 questions

Open this lesson in Kodokon

OWASP maintains a top 10 dedicated to APIs. The three dominant risks: BOLA (Broken Object Level Authorization - accessing another user's object by changing an id), Broken Authentication (poorly validated tokens, weak secrets), and Unrestricted Resource Consumption (no limit on rate, size, or time). Notice that none of the three is fixed by installing a library: they are properties of your design. Let's start with the cross-cutting mechanisms: CORS, rate limiting, and headers.

JAVASCRIPT
import { createServer } from "node:http";

const allowed = new Set(["https://app.example.com"]);

const server = createServer((req, res) => {
  const origin = req.headers.origin;
  if (origin && allowed.has(origin)) {
    res.setHeader(
      "Access-Control-Allow-Origin",
      origin,
    );
    res.setHeader("Vary", "Origin");
  }
  if (req.method === "OPTIONS") {
    res.setHeader(
      "Access-Control-Allow-Methods",
      "GET,POST,PUT,DELETE",
    );
    res.setHeader(
      "Access-Control-Allow-Headers",
      "content-type,authorization",
    );
    res.setHeader("Access-Control-Max-Age", "86400");
    res.writeHead(204).end();
    return;
  }
  res.setHeader("Content-Type", "application/json");
  res.end(JSON.stringify({ ok: true }));
});

server.listen(3000);
CORS by hand: allowlist and preflight.

Understand clearly what CORS is not: server protection. curl or a third-party backend completely ignore these headers; only the browser enforces them, to protect its users. Three expert rules: return the exact origin from an allowlist (never blindly echo the Origin header), add Vary: Origin so you don't poison intermediate caches, and know that the * wildcard is rejected by browsers as soon as the request carries cookies (credentials).

JAVASCRIPT
const WINDOW_MS = 60_000;
const LIMIT = 100;
const hits = new Map();

function check(ip) {
  const now = Date.now();
  const entry = hits.get(ip);
  if (!entry || now - entry.start >= WINDOW_MS) {
    hits.set(ip, { start: now, count: 1 });
    return { ok: true, remaining: LIMIT - 1 };
  }
  entry.count += 1;
  const remaining = Math.max(LIMIT - entry.count, 0);
  return { ok: entry.count <= LIMIT, remaining };
}

console.log(check("203.0.113.7"));
Fixed window in memory: return 429 if ok is false.

Security headers cost a few lines and block entire classes of attacks: HSTS forces HTTPS for future visits, X-Content-Type-Options: nosniff prevents the browser from guessing a MIME type, and a restrictive CSP (default-src 'none') neutralizes the exploitation of an API response mistakenly rendered in a browser. Also remove any header that reveals your stack: Express adds X-Powered-By by default - disable it with app.disable('x-powered-by').

JAVASCRIPT
function setSecurityHeaders(res) {
  res.setHeader(
    "Strict-Transport-Security",
    "max-age=63072000; includeSubDomains",
  );
  res.setHeader("X-Content-Type-Options", "nosniff");
  res.setHeader(
    "Content-Security-Policy",
    "default-src 'none'; frame-ancestors 'none'",
  );
  res.setHeader("Cache-Control", "no-store");
}

export { setSecurityHeaders };
To call on every API response.

Knowledge check

Make sure you remember the key points of this lesson.

  1. What does the CORS mechanism actually protect?
    • The server against automated requests (curl, scripts)
    • The browser user against unauthorized cross-origin reads
    • The API against denial-of-service attacks
  2. Why is Access-Control-Allow-Origin: * incompatible with requests sending cookies?
    • Browsers reject this combination to avoid exposing authenticated sessions to any site
    • The server automatically returns a 500 error
    • The wildcard disables cookies server-side
  3. An authenticated API returns GET /invoices/42 to a user who doesn't own that invoice. Which OWASP API risk is this?
    • Injection
    • Broken Object Level Authorization (BOLA)
    • Security Misconfiguration
    • Server-Side Request Forgery