Rate Limiting en APIs: Protección Contra Abuso
El rate limiting es una técnica esencial para proteger APIs contra abuso, consumo excesivo de recursos y ataques de denegación de servicio. Una implementación adecuada garantiza la disponibilidad para usuarios legítimos mientras bloquea comportamientos maliciosos o excesivos.
¿Por Qué Implementar Rate Limiting?
- Protección Contra DDoS: Mitigar ataques de denegación de servicio
- Prevenir Scraping: Dificultar la extracción automatizada de datos
- Controlar Costos: Evitar el consumo excesivo de recursos computacionales
- Garantizar la Calidad del Servicio: Distribuir recursos de forma equitativa
- Prevenir Brute Force: Limitar los intentos de autenticación
Algoritmos de Rate Limiting
1. Token Bucket
Algoritmo que mantiene un "cubo" de tokens que se rellenan a lo largo del tiempo:
class TokenBucket {
constructor(capacity, refillRate) {
this.capacity = capacity; // Capacidade máxima do balde
this.tokens = capacity; // Tokens disponíveis
this.refillRate = refillRate; // Tokens por segundo
this.lastRefill = Date.now();
}
tryConsume(tokens = 1) {
this.refill();
if (this.tokens >= tokens) {
this.tokens -= tokens;
return true; // Requisição permitida
}
return false; // Rate limit excedido
}
refill() {
const now = Date.now();
const timePassed = (now - this.lastRefill) / 1000;
const tokensToAdd = timePassed * this.refillRate;
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
// Uso: 100 requisições máximo, recarrega 10/segundo
const bucket = new TokenBucket(100, 10);
Ventajas: Permite bursts controlados, suaviza el tráfico
Desventajas: Más complejo de implementar
2. Leaky Bucket
Procesa solicitudes a una tasa constante, como agua que gotea de un cubo:
- Las solicitudes entran en el cubo
- Se procesan a una tasa fija
- El exceso se desborda (rechazado)
- Garantiza una salida uniforme
3. Fixed Window
Cuenta las solicitudes en ventanas de tiempo fijas:
class FixedWindowRateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = new Map();
}
isAllowed(userId) {
const now = Date.now();
const windowStart = Math.floor(now / this.windowMs) * this.windowMs;
const key = \`\$:\$\`;
const count = this.requests.get(key) || 0;
if (count < this.maxRequests) {
this.requests.set(key, count + 1);
return true;
}
return false;
}
}
// 100 requisições por hora
const limiter = new FixedWindowRateLimiter(100, 60 * 60 * 1000);
Problema: Permite hasta 2x el límite en el borde de las ventanas
4. Sliding Window Log
Mantiene un registro de las marcas de tiempo de las solicitudes:
- Almacena la marca de tiempo de cada solicitud
- Elimina las solicitudes fuera de la ventana
- Más preciso que Fixed Window
- Mayor consumo de memoria
5. Sliding Window Counter
Combina Fixed Window con suavizado:
// Calcula uma média ponderada entre janelas atual e anterior
const currentWindowCount = getCurrentWindowCount(userId);
const previousWindowCount = getPreviousWindowCount(userId);
const percentageInCurrentWindow = (now - currentWindowStart) / windowSize;
const estimatedCount =
previousWindowCount * (1 - percentageInCurrentWindow) +
currentWindowCount;
return estimatedCount < maxRequests;
Implementación Práctica
Con Redis (Recomendado para Producción)
import Redis from 'ioredis';
const redis = new Redis();
async function checkRateLimit(userId, maxRequests = 100, windowSeconds = 60) {
const key = \`rate_limit:\$\`;
const now = Date.now();
const windowStart = now - (windowSeconds * 1000);
// Remover requisições antigas
await redis.zremrangebyscore(key, 0, windowStart);
// Contar requisições na janela
const requestCount = await redis.zcard(key);
if (requestCount < maxRequests) {
// Adicionar nova requisição
await redis.zadd(key, now, \`\$-\${Math.random()}\`);
await redis.expire(key, windowSeconds);
return { allowed: true, remaining: maxRequests - requestCount - 1 };
}
return { allowed: false, remaining: 0 };
}
// Middleware Express
app.use(async (req, res, next) => {
const userId = req.user?.id || req.ip;
const result = await checkRateLimit(userId);
res.set({
'X-RateLimit-Limit': 100,
'X-RateLimit-Remaining': result.remaining,
'X-RateLimit-Reset': new Date(Date.now() + 60000).toISOString()
});
if (!result.allowed) {
return res.status(429).json({
error: 'Too Many Requests',
retryAfter: 60
});
}
next();
});
Bibliotecas Populares
- express-rate-limit: Middleware para Express.js
- rate-limiter-flexible: Admite múltiples backends (Redis, Memcached, MySQL)
- Kong Rate Limiting: Plugin para API Gateway
- AWS API Gateway: Rate limiting nativo
Estrategias Avanzadas
Rate Limiting Jerárquico
- Global: Límite total de la API (ej.: 1M req/min)
- Por Usuario: Límite individual (ej.: 1000 req/min)
- Por Endpoint: Límites específicos (login: 5 req/min)
- Por IP: Protección adicional contra abusos
Rate Limiting Dinámico
- Ajustar los límites según la carga del sistema
- Aumentar los límites para usuarios premium
- Reducir los límites durante incidentes
Whitelisting y Blacklisting
- Eximir a IPs/usuarios de confianza
- Bloquear permanentemente a atacantes conocidos
- Implementar un sistema de reputación
Mejores Prácticas
- Devolver cabeceras informativas (X-RateLimit-*)
- Usar el estado HTTP 429 (Too Many Requests)
- Incluir la cabecera Retry-After
- Documentar los límites claramente en la API
- Implementar backoff exponencial en el cliente
- Monitorear las métricas de rate limiting
- Alertar sobre patrones anormales
- Probar los límites antes de producción
Herramientas de Monitoreo
- Grafana + Prometheus: Visualizar métricas de rate limiting
- Datadog: Monitoreo y alertas
- CloudWatch: Para APIs en AWS
- New Relic: APM con soporte para rate limiting
