Kodokon kodokon.com

Asegurar una API en producción

Endurece una API de Node en producción: CORS correcto, rate limiting, cabeceras de seguridad y defensas contra los principales riesgos de la OWASP API.

11 min · 3 preguntas

Abrir esta lección en Kodokon

OWASP mantiene un top 10 dedicado a las APIs. Los tres riesgos dominantes: BOLA (Broken Object Level Authorization - acceder al objeto de otro usuario cambiando un id), Broken Authentication (tokens mal validados, secretos débiles) y Unrestricted Resource Consumption (sin límite de tasa, tamaño o tiempo). Fíjate en que ninguno de los tres se soluciona instalando una biblioteca: son propiedades de tu diseño. Empecemos por los mecanismos transversales: CORS, rate limiting y cabeceras.

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 a mano: lista de permitidos y preflight.

Entiende claramente lo que CORS no es: una protección del servidor. curl o un backend de terceros ignoran por completo estas cabeceras; solo el navegador las aplica, para proteger a sus usuarios. Tres reglas de experto: devuelve el origen exacto desde una lista de permitidos (nunca reenvíes a ciegas la cabecera Origin), añade Vary: Origin para no envenenar las cachés intermedias, y ten en cuenta que el comodín * es rechazado por los navegadores en cuanto la petición lleva 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"));
Ventana fija en memoria: devuelve 429 si ok es false.

Las cabeceras de seguridad cuestan unas pocas líneas y bloquean clases enteras de ataques: HSTS fuerza HTTPS para las visitas futuras, X-Content-Type-Options: nosniff impide que el navegador adivine un tipo MIME, y una CSP restrictiva (default-src 'none') neutraliza la explotación de una respuesta de API renderizada por error en un navegador. Elimina también cualquier cabecera que revele tu stack: Express añade X-Powered-By por defecto - desactívalo con 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 };
Para llamar en cada respuesta de la API.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué protege realmente el mecanismo CORS?
    • El servidor contra peticiones automatizadas (curl, scripts)
    • Al usuario del navegador contra lecturas cross-origin no autorizadas
    • La API contra ataques de denegación de servicio
  2. ¿Por qué Access-Control-Allow-Origin: * es incompatible con peticiones que envían cookies?
    • Los navegadores rechazan esta combinación para evitar exponer sesiones autenticadas a cualquier sitio
    • El servidor devuelve automáticamente un error 500
    • El comodín desactiva las cookies en el servidor
  3. Una API autenticada devuelve GET /invoices/42 a un usuario que no es dueño de esa factura. ¿Qué riesgo de la OWASP API es este?
    • Injection
    • Broken Object Level Authorization (BOLA)
    • Security Misconfiguration
    • Server-Side Request Forgery