Module B02

Rate Limiting & DDoS

Protégez vos API contre le brute force, le scraping et les attaques volumétriques.

01 Pourquoi rate limiter ?

Une API sans rate limiting est exposée à plusieurs classes d'attaques :

AttaqueDescriptionImpact
Brute forceDes milliers de tentatives de mot de passe sur /loginCompromission de comptes
Credential stuffingRejouer des paires email/mdp issues de fuitesAccount takeover massif
ScrapingRécupération automatique de tout le catalogueVol de données, coûts serveur
DDoS applicatifSaturer les endpoints lourds (recherche, rapports)Indisponibilité du service
API abuseDépasser les quotas d'usage prévu (coûts cloud)Surcoûts imprévisibles
💡 Le rate limiting n'empêche pas toutes ces attaques seul — il les rend économiquement non viables pour l'attaquant.

02 Stratégies de rate limiting

🪟 Fixed Window

Compte les requêtes sur une fenêtre fixe (ex: 100 req/minute). Simple mais vulnérable aux bursts en fin/début de fenêtre.

🌊 Sliding Window

Fenêtre glissante — compte les N dernières secondes en temps réel. Lisse les pics, plus précis que Fixed.

🪣 Token Bucket

Chaque client a un "seau" de tokens. Chaque requête consomme 1 token. Le seau se remplit progressivement. Autorise les bursts contrôlés.

💧 Leaky Bucket

Les requêtes entrent dans une file FIFO à débit constant. Lisse parfaitement le trafic. Idéal pour les APIs à débit garanti.

StratégieBurst autoriséComplexitéUsage typique
Fixed WindowOui (en transition)SimpleAPIs simples, MVP
Sliding WindowNonModéréeAuth, APIs sensibles
Token BucketOui (contrôlé)ModéréeAPIs publiques, SDK
Leaky BucketNonÉlevéeIntégrations, webhooks

03 express-rate-limit — configuration

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

// Limiter global : 100 req / 15 min par IP
const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,
  message: { error: 'Trop de requêtes, réessayez dans 15 min.' },
  standardHeaders: true,  // Renvoie RateLimit-* headers (RFC 6585)
  legacyHeaders: false,  // Désactive X-RateLimit-* legacy
});

// Limiter auth : 5 req / 15 min par IP (brute force)
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: { error: 'Trop de tentatives de connexion.' },
  skipSuccessfulRequests: true, // Ne compte pas les connexions réussies
});

app.use(globalLimiter);
app.use('/api/auth', authLimiter);

Options importantes

OptionTypeDescription
windowMsnumberDurée de la fenêtre en millisecondes
maxnumber | fnNombre max de requêtes par fenêtre
keyGeneratorfunctionClé d'identification du client (défaut: IP)
skipfunctionRetourner true pour ignorer cette requête
skipSuccessfulRequestsbooleanNe compte pas les réponses 2xx
handlerfunctionHandler personnalisé quand la limite est atteinte
storeStoreBackend de stockage (mémoire, Redis…)

04 Rate limiting différencié par route

Toutes les routes n'ont pas le même profil de risque. Une bonne stratégie différencie :

// Routes d'authentification — très restrictif (brute force)
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 min
  max: 5,
  keyGenerator: (req) => req.body?.email || req.ip, // Par email + IP
});

// API données — modéré
const dataLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 min
  max: 100,
});

// Endpoints publics (documentation, statut)
const publicLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 500,
});

app.use('/auth/login', authLimiter);
app.use('/auth/register', authLimiter);
app.use('/api/data', dataLimiter);
app.use('/docs', publicLimiter);

// keyGenerator par utilisateur authentifié
const perUserLimiter = rateLimit({
  keyGenerator: (req) => req.user?.id || req.ip,
  max: 1000,
  windowMs: 60 * 60 * 1000, // 1h
});
⚠️ Le rate limiting par IP seul est insuffisant si l'attaquant utilise un botnet ou des proxies rotatifs. Combinez IP + user ID + fingerprint.

05 Redis pour le rate limiting distribué

Par défaut, express-rate-limit stocke les compteurs en mémoire — chaque instance de votre serveur a son propre état. Avec 3 instances derrière un load balancer, un client peut envoyer 3× la limite autorisée.

const { RedisStore } = require('rate-limit-redis');
const { createClient } = require('redis');

const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  store: new RedisStore({
    sendCommand: (...args) => redisClient.sendCommand(args),
  }),
});
StockageMulti-instancePerformanceUsage
Mémoire (défaut)NonTrès rapideDéveloppement, instance unique
RedisOuiRapide (sub-ms)Production, clusters
MongoDB/SQLOuiModéréeSi Redis non disponible