Skip to content

Protege tu API con Node y Express

Posted on:30 de agosto de 2023 at 22:00

Cómo Proteger tu API con Node.js y Express

Las APIs (Interfaces de Programación de Aplicaciones) se han convertido en componentes esenciales para el desarrollo de aplicaciones modernas. Sin embargo, a medida que las APIs se vuelven más cruciales, también se vuelven más susceptibles a amenazas de seguridad. Proteger tu API es crucial para garantizar la integridad de tus datos y la privacidad de tus usuarios. En este artículo, exploraremos cómo puedes proteger tu API utilizando Node.js y Express, dos tecnologías populares en el desarrollo web.

Las mejores prácticas de seguridad en el servidor

Utilizar HTTPS

El primer paso para proteger tu API es asegurarte de que las comunicaciones entre el cliente y el servidor estén encriptadas. Utilizar HTTPS (Protocolo Seguro de Transferencia de Hipertexto) es fundamental para evitar que los datos sean interceptados por terceros malintencionados. Puedes obtener un certificado SSL/TLS de una autoridad de certificación confiable para habilitar HTTPS en tu servidor Node.js.

const https = require('https');
const fs = require('fs');
const express = require('express');

const app = express();
const options = {
  key: fs.readFileSync('ruta/a/clave-privada.key'),
  cert: fs.readFileSync('ruta/a/certificado.crt')
};

const server = https.createServer(options, app);

server.listen(3000, () => {
  console.log('Listening...');
});

Validar datos de entrada

La validación de datos de entrada es esencial para prevenir ataques como la inyección de SQL y el cross-site scripting (XSS). Utiliza bibliotecas como express-validator para validar y sanitizar los datos de entrada antes de procesarlos en tu API.

const { body, validationResult } = require('express-validator');
const express = require('express');
const app = express();

app.post('/singup', [
  body('email').isEmail(),
  body('password').isStrongPassword(),
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errores: errors.array() });
  }
  // Process data...
});

Autenticación y Autorización

Implementa un sistema de autenticación sólido para asegurarte de que solo los usuarios autorizados puedan acceder a tu API. Puedes utilizar estrategias de autenticación como JWT (Tokens de Acceso JSON) o OAuth.

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();

// Authentication middleware
const verificarToken = (req, res, next) => {
  const token = req.header('Authorization');

  if (!token) {
    return res.status(401).json({ mensaje: 'Access denied.' });
  }

  try {
    const decodificado = jwt.verify(token, 'secret');
    req.usuario = decodificado.usuario;
    next();
  } catch (error) {
    res.status(401).json({ mensaje: 'Invalid token.' });
  }
};

app.get('/protected-data', verificarToken, (req, res) => {
  // Access allowed for authenticated users only
});

Limitar las solicitudes

Para evitar ataques de denegación de servicio (DoS), puedes implementar límites en las solicitudes que tu API puede manejar en un intervalo de tiempo determinado utilizando bibliotecas como express-rate-limit.

Limitar por IP

const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // maximum 100 requests per IP in the interval,
  standardHeaders: true,
});

app.use(limiter);

Limitar por usuario

const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();

// Authentication middleware
// ...

// Rate limiting middleware
const userRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Maximum requests per time window
  keyGenerator: (req) => req.user.id, // Key generator based on the user ID
  handler: (req, res) => {
    res.status(429).json({ error: 'Speed limit reached. Try again later.' });
  },
});

app.use(userRateLimiter);

app.get('/protected', [verificarToken, userRateLimiter], (req, res) => {
  res.json({ message: 'Acceso concedido a la ruta protegida' });
});


app.listen(3000, () => {
  console.log('listen on port 3000');
});

Limitar por tamaño de cabezera

Esto sirve para proteger tu aplicación contra el ataque de desbordamiento de búfer.

const express = require('express');
const app = express();
app.use(express.json());

const limitPayloadSize = (req, res, next) => {
  const MAX_PAYLOAD_SIZE = 1024 * 1024; // 1MB
  if (req.headers['content-length'] && parseInt(req.headers['content-length']) > MAX_PAYLOAD_SIZE){
    return res.status(413).json({ error: 'Payload size exceeds the limit' });
  }
  next();
}

app.use(limitPayloadSize);

app.listen(3000, () => {
  console.log('listen on port 3000')
});

También se puede hacer usando body-parser o un reverse proxy.

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.use(bodyParser.json({ limit: '1mb' }));

app.listen(3000, () => {
  console.log('listen on port 3000');
});

Implementar CORS

Por defecto, los navegadores aplican la “Política del mismo origen” (Same-Origin Policy), que impide que un script en un sitio web acceda a recursos en otro dominio. Pero si tu API permite solicitudes desde diferentes dominios (CORS), configura cuidadosamente las opciones de CORS para evitar que sitios no autorizados accedan a tu API.

const express = require('express');
const cors = require('cors');
const app = express();

const opcionesCors = {
  origin: ['https://domain1.com', 'https://domain2.com'],
  methods: 'GET,PUT,POST,DELETE',
};

app.use(cors(opcionesCors));

Utilizar Helmet

Helmet es una colección de funciones de middleware que establecen cabeceras HTTP relacionadas con la seguridad.

const express = require('express');
const helmet = require('helmet');

const app = express();

app.use(helmet());

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

Como mínimo, inhabilitar la cabecera X-Powered-By

Si no quiere utilizar Helmet por lo menos inhabilite la cabecera X-Powered-By. Los atacantes pueden utilizar esta cabecera (que está habilitada de forma predeterminada) para detectar las aplicaciones que ejecutan Express e iniciar ataques con destinos específicos.

app.disable('x-powered-by');

Monitoriza y Registra Actividades

El registro y la monitorización son increíblemente importantes para una seguridad consistente en Node.js. Monitorizar tus registros te da una visión de lo que está pasando en tu aplicación para que puedas investigar cualquier cosa sospechosa. Algunos niveles importantes de registro son info, error, warn y debug.

const express = require("express");
const winston = require("winston");
const app = express();

const logger = winston.createLogger({
  level: "debug",
  format: winston.format.json(),
  transports: [new winston.transports.Console()],
});

app.get("/", (req, res, next) => {
  logger.debug("Home '/' route.");
  res.status(200).send("Logging Hello World..");
});

app.get("/data", (req, res, next) => {
  try {
    throw new Error("Not found!");
  } catch (error) {
    logger.error("Data Error: Not found");
    res.status(404).send("Error!");
  }
});

app.listen(3000, () => {
  logger.info("Server Listening On Port 3000");
});

Mantener dependencias actualizadas

Las vulnerabilidades de seguridad pueden surgir de las dependencias desactualizadas. Utiliza herramientas como npm audit para verificar las vulnerabilidades en tus paquetes y asegúrate de mantener tus dependencias actualizadas.

Npm audit

Npm audit fix