← Servidor web y API con Node

Sesión 14 · Semana 5

Seguridad mínima

Hoy · Hoja de ruta

  1. 1. Aprende: Las protecciones que no pueden faltar en un servicio expuesto a internet.
  2. 2. Haz: Aplícalas a tu API y comprueba que funcionan.
  3. 3. Comprueba: Intentas romper tu propio servicio y no lo consigues.

Antes de empezar · 5 minutos, sin apuntes

  1. ¿Qué podría hacer alguien con acceso a tu API tal como está hoy?
  2. ¿Qué pasa si envía un cuerpo de cien megabytes?
  3. ¿Y si llama mil veces por segundo?

La lista mínima

Riesgo Protección
Datos inválidos o inesperados Validación con lista blanca en el servidor
Inyección de HTML Escapar todo dato al generar la página
Salirse de la carpeta pública Comprobar la ruta resuelta
Cuerpos enormes Límite de tamaño en el analizador
Abuso de llamadas Limitación por dirección y ventana de tiempo
Cabeceras que delatan Quitar la que anuncia el framework
Escritura sin permiso Autorización en las rutas que modifican
Secretos filtrados Todo en el entorno, nada en el repositorio

Aplicarlas

app.disable("x-powered-by");
app.use(express.json({ limit: "100kb" }));

const llamadas = new Map();

export function limitar(peticion, respuesta, next) {
  const clave = peticion.ip;
  const ahora = Date.now();
  const registro = llamadas.get(clave) ?? { desde: ahora, total: 0 };

  if (ahora - registro.desde > 60_000) {
    registro.desde = ahora;
    registro.total = 0;
  }

  registro.total += 1;
  llamadas.set(clave, registro);

  if (registro.total > 100) {
    return respuesta.status(429).json({ error: "Demasiadas peticiones", codigo: "LIMITE" });
  }
  next();
}

Es un limitador de andar por casa —vive en memoria y se pierde al reiniciar— pero enseña la idea, y el 429 es el código que corresponde.

Autorización para lo que modifica

export function requiereClave(peticion, respuesta, next) {
  const enviada = peticion.headers["x-api-key"];
  if (enviada !== configuracion.claveApi) {
    throw new ErrorNoAutenticado("Clave de API no válida");
  }
  next();
}

router.post("/", requiereClave, crearProducto);
router.delete("/:id", requiereClave, borrarProducto);

Leer es público; escribir, no

Una clave en una cabecera es la forma más simple de proteger las operaciones que modifican, y es suficiente para un proyecto de aula. No es un sistema de usuarios: no distingue quién llama, no caduca y quien la tenga puede todo.

Lo que sí enseña es dónde se pone la comprobación —en el servidor, antes del manejador— y la diferencia entre 401 y 403: la primera es «no sé quién eres», la segunda «sé quién eres y no puedes».

Una regla no admite excepción: si algún día almacenas contraseñas, se guardan cifradas con una función pensada para eso, nunca en claro ni con un resumen sin sal. En este módulo directamente no guardamos ninguna.

Tarea 14 · Blindar

  1. Aplica el límite de tamaño y quita la cabecera del framework.
  2. Escribe el limitador de peticiones y devuelve 429.
  3. Protege con clave las rutas que modifican.
  4. Repasa que toda entrada sigue validándose con lista blanca.
  5. Intenta atacar tu propio servicio de cinco formas distintas y anota el resultado.
  6. Ejecuta npm audit y resuelve lo que aparezca.
Objetivo mínimoLas seis protecciones aplicadas y comprobadas.
Si lo tienesDevuelve las cabeceras que dicen cuántas llamadas quedan.
RetoEscribe un informe con los riesgos que tu servicio sigue teniendo y cómo se resolverían.

Checkpoint · fin de la sesión 14

  • Las operaciones que modifican exigen autorización.
  • Hay límites de tamaño y de frecuencia.
  • Ningún secreto está en el repositorio.
  • Has intentado romper tu propio servicio.
Ver respuestas

1 · Un 429.

2 · 401 es no autenticado; 403, autenticado pero sin permiso.

3 · En el servidor, antes del manejador de la ruta.