Middleware de Seguridad
Refuerza las API de Express con helmet para encabezados de seguridad, cors para la política de origen cruzado y limitación de tasa para la prevención de abusos.
Receta
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
import express from "express";
import helmet from "helmet";
import cors from "cors";
import rateLimit from "express-rate-limit";
const app = express();
app.set("trust proxy", 1);
app.use(helmet());
app.use(cors({
origin: process.env.ALLOWED_ORIGIN ?? "https://app.example.com",
credentials: true,
}));
app.use(express.json({ limit: "1mb" }));
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
}));Cuándo usarlo: Cada API pública. El middleware de seguridad no es opcional para los endpoints de producción.
Ejemplo de Funcionamiento
import express from "express";
import helmet from "helmet";
import cors from "cors";
import rateLimit from "express-rate-limit";
const app = express();
app.set("trust proxy", 1);
app.use(helmet({
contentSecurityPolicy: false, // habilítalo si sirves HTML desde Express
crossOriginResourcePolicy: { policy: "cross-origin" },
}));
const allowedOrigins = ["https://app.example.com", "https://staging.example.com"];
app.use(cors({
origin(origin, callback) {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error("No permitido por CORS"));
}
},
credentials: true,
}));
const apiLimiter = rateLimit({
windowMs: 60_000,
max: 60,
standardHeaders: true,
legacyHeaders: false,
});
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: { error: "Demasiados intentos de inicio de sesión" },
});
app.use(express.json({ limit: "1mb" }));
app.use("/api", apiLimiter);
app.post("/api/auth/login", authLimiter, loginHandler);
app.get("/health", (_req, res) => res.json({ ok: true }));
function loginHandler(_req: express.Request, res: express.Response) {
res.json({ token: "..." });
}Lo que esto demuestra:
helmetestablece encabezados de seguridad (X-Content-Type-Options, HSTS, etc.)- Lista de permitidos de origen CORS dinámica
- Límite de tasa de API general más un límite de endpoint de autenticación más estricto
- Verificación de salud exenta de middleware pesado (montada antes del alcance del limitador)
Análisis Detallado
Cómo Funciona
- helmet: Establece encabezados de respuesta HTTP que mitigan XSS, clickjacking y el rastreo de tipos MIME
- cors: Agrega encabezados
Access-Control-*; los navegadores aplican CORS, las llamadas de servidor a servidor lo ignoran - rate-limit: Rastrea las solicitudes por IP (o clave personalizada) en memoria o en un almacén de Redis
Hoja de Trucos de Encabezados
| Encabezado | Establecido por | Propósito |
|---|---|---|
Strict-Transport-Security | helmet | Fuerza HTTPS |
X-Content-Type-Options | helmet | Previene el rastreo de tipos MIME |
Access-Control-Allow-Origin | cors | Origen de navegador permitido |
RateLimit-Remaining | rate-limit | Cuota visible para el cliente |
Almacén de Límite de Tasa de Producción
import { RedisStore } from "rate-limit-redis";
import { createClient } from "redis";
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
const limiter = rateLimit({
store: new RedisStore({ sendCommand: (...args) => redis.sendCommand(args) }),
windowMs: 60_000,
max: 100,
});Errores Comunes
- CORS
origin: *con credenciales - los navegadores rechazan esta combinación. Solución: especifica orígenes exactos. - Limitador de tasa antes de trust proxy - un solo cubo para todos los usuarios. Solución: confía primero en el proxy.
- CSP de helmet que rompe scripts en línea - está bien para API JSON; se rompe si Express sirve HTML. Solución: deshabilita CSP o configura directivas.
- Sin límite de tasa en los endpoints de autenticación - ataques de fuerza bruta de inicio de sesión. Solución: limitador estricto en
/auth/login. - CORS no es autenticación - las llamadas de servidor a servidor y curl omiten CORS por completo. Solución: valida siempre los tokens en el lado del servidor.
- Límite de tasa en memoria con múltiples pods - cada pod tiene su propio contador. Solución: almacén de Redis para el conteo distribuido.
Alternativas
| Alternativa | Usar Cuándo | No Usar Cuándo |
|---|---|---|
| Limitación de tasa de gateway API | Kong, AWS API Gateway al frente | Implementación simple de un solo servicio |
| Cloudflare WAF | Protección DDoS y bot en el borde | API solo interna |
Fastify @fastify/helmet | Pila de Fastify | Código base de Express |
NestJS ThrottlerModule | NestJS con límites de decorador | Express simple |
Preguntas Frecuentes
¿Necesito CORS para una API de aplicación móvil?
Las aplicaciones móviles nativas no aplican CORS. Aún necesitas autenticación. CORS es importante para clientes basados en navegador (SPA, panel de administración).
¿Debería usarse helmet para API solo JSON?
Sí. Los encabezados de seguridad protegen contra ataques inesperados de tipo de contenido y mejoran las puntuaciones de los escáneres de seguridad.
¿Qué límite de tasa es razonable?
Comienza con 100 solicitudes/minuto por IP para API general, 10/15min para inicio de sesión. Ajusta según los patrones de tráfico reales.
¿Cómo eximo los webhooks de los límites de tasa?
Monta las rutas de webhook antes del limitador o usa una función de omisión que verifique la ruta.
¿CORS maneja el preflight?
Sí. El paquete cors responde a OPTIONS automáticamente cuando está configurado.
¿Qué pasa con CSRF para la autenticación basada en cookies?
CORS no previene CSRF. Usa csurf o cookies SameSite con tokens de doble envío para sesiones de cookies.
¿Puedo usar helmet con Fastify?
Sí, a través de @fastify/helmet. Se aplican los mismos conceptos.
¿Cómo pruebo los encabezados de seguridad?
curl -sI https://api.example.com/health y verifica Strict-Transport-Security, X-Content-Type-Options, etc.
Relacionado
- Orden de Middleware - secuencia de registro
- Conciencia del Proxy Inverso - confía en el proxy para límites de tasa
- Conceptos Básicos de Seguridad - postura de seguridad más amplia
- OWASP Top 10 para APIs - modelo de amenazas
- Mejores Prácticas de Express - lista de verificación de la sección
Versiones de la pila: Esta página fue escrita para Node.js 24.18.0 (LTS activa), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.