Rate limiting en API security in Node.js

Leer hoe je rate limiting en API security inricht in Node.js. Van token buckets tot auth, CORS en headers, met praktische code en best practices.

27 juli 20267 min leestijdDoor We Develop Communication

Een publieke Node.js API zonder rate limiting is als een winkel zonder deur: iedereen kan naar binnen, zo vaak als ze willen, met zoveel als ze willen. Rate limiting en API security horen daarom standaard op je checklist wanneer je een backend bouwt die klaar is voor productie.

In dit artikel leer je hoe je rate limiting implementeert in Node.js, welke strategieën er zijn, en hoe je dit combineert met andere security-maatregelen zoals authenticatie, CORS en security headers. Met praktische codevoorbeelden die je direct kunt toepassen.

Waarom rate limiting onmisbaar is

Zonder limieten is je API kwetsbaar voor verschillende problemen. Brute-force aanvallen op login-endpoints, scraping van data, denial-of-service door één misconfigured client, en onverwachte kostenpieken bij externe dependencies zoals databases of AI-API's.

Rate limiting lost dit op door een maximum te zetten op het aantal requests per client per tijdseenheid. Dat kan per IP, per API-key, per user of per endpoint. Goede rate limiting is onzichtbaar voor legitieme gebruikers, maar blokkeert misbruik effectief.

Het is bovendien een bouwsteen van je bredere error handling en observability-strategie. Zie ook onze gids over error handling op schaal.

De belangrijkste rate limiting strategieën

Voordat je code schrijft, is het goed om de vier meest voorkomende algoritmen te kennen. Elk heeft eigen trade-offs.

Fixed window

Je deelt tijd op in vaste vensters (bijvoorbeeld 1 minuut) en telt requests per venster. Simpel te implementeren, maar een client kan op de grens van twee vensters ineens het dubbele verkeer genereren.

Sliding window

Hier schuift het venster mee met de tijd. Je krijgt een gelijkmatigere verdeling maar het vraagt meer geheugen of slimmere datastructuren.

Token bucket

Een client heeft een "emmer" met tokens die met een vaste rate wordt aangevuld. Elke request kost een token. Is de emmer leeg, dan wordt de request geweigerd. Dit algoritme staat bursts toe tot een maximum, wat prettig is voor echte gebruikers.

Leaky bucket

Vergelijkbaar met token bucket maar verwerkt requests met een vaste snelheid. Bursts worden afgevlakt. Handig voor downstream-systemen die geen pieken aankunnen.

Voor de meeste Node.js API's is een token bucket of sliding window de juiste keuze.

Basis rate limiting met Express

De snelste manier om te starten is met express-rate-limit. Dit is een middleware die in-memory of via een externe store werkt.

npm install express-rate-limit
import rateLimit from 'express-rate-limit';
import express from 'express';

const app = express();

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minuten
  max: 100, // max 100 requests per IP
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Te veel requests. Probeer het later opnieuw.' },
});

app.use('/api/', apiLimiter);

Met standardHeaders: true stuur je automatisch RateLimit-Limit, RateLimit-Remaining en RateLimit-Reset headers mee. Clients kunnen daarop anticiperen.

Meer over hoe middleware werkt in Express vind je in ons artikel over middleware patterns in Node.js.

Striktere limieten voor gevoelige endpoints

Niet elk endpoint verdient dezelfde behandeling. Een login- of wachtwoord-reset-endpoint heeft veel lagere limieten nodig om brute-force aanvallen te stoppen.

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5, // slechts 5 pogingen per 15 minuten
  skipSuccessfulRequests: true, // alleen falende pogingen tellen
  message: { error: 'Te veel inlogpogingen. Probeer het later opnieuw.' },
});

app.post('/auth/login', loginLimiter, loginHandler);

Door skipSuccessfulRequests tel je alleen mislukte logins mee. Normale gebruikers die per ongeluk hun wachtwoord verkeerd typen worden zo minder snel geblokkeerd.

Meer over veilige login-flows lees je in ons artikel authentication in Node.js: JWT vs sessions.

Distributed rate limiting met Redis

Draai je meerdere instances (zie ook clustering en scaling in Node.js), dan werkt in-memory rate limiting niet meer. Elke instance telt dan zijn eigen requests en de limiet wordt feitelijk vermenigvuldigd met het aantal instances.

De oplossing is een gedeelde store, meestal Redis.

npm install rate-limit-redis ioredis
import { RateLimiterRedis } from 'rate-limiter-flexible';
import Redis from 'ioredis';

const redis = new Redis({
  host: process.env.REDIS_HOST,
  enableOfflineQueue: false,
});

const rateLimiter = new RateLimiterRedis({
  storeClient: redis,
  keyPrefix: 'rl',
  points: 100,       // requests
  duration: 60,      // per 60 seconden
  blockDuration: 60, // blokkeer 60 sec bij overschrijding
});

app.use(async (req, res, next) => {
  try {
    await rateLimiter.consume(req.ip);
    next();
  } catch (rejRes) {
    res.set('Retry-After', String(Math.ceil(rejRes.msBeforeNext / 1000)));
    res.status(429).json({ error: 'Too Many Requests' });
  }
});

rate-limiter-flexible ondersteunt meerdere algoritmen en stores en is een goede keuze voor productie. Zie de officiële documentatie voor advanced gebruik.

Rate limiten op user in plaats van IP

Voor geauthenticeerde endpoints is rate limiten op IP niet altijd ideaal. Meerdere gebruikers achter één NAT delen een IP en zouden onterecht geraakt worden. Limiteer liever op user ID of API key.

const userLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 60,
  keyGenerator: (req) => req.user?.id || req.ip,
});

app.use('/api/', authenticate, userLimiter);

Combineer dit met een strakke IP-limiter op publieke endpoints en je dekt beide scenario's.

Beveilig je headers met Helmet

Rate limiting is één laag. Voor brede API security voeg je security headers toe. Het populairste pakket hiervoor is Helmet.

npm install helmet
import helmet from 'helmet';

app.use(helmet());

Helmet zet headers zoals Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options en Content-Security-Policy. Deze beschermen tegen clickjacking, MIME-sniffing aanvallen en XSS.

CORS correct configureren

Een veelgemaakte fout is een permissieve CORS-configuratie zoals Access-Control-Allow-Origin: * op endpoints die credentials verwerken. Wees specifiek over welke origins je toelaat.

import cors from 'cors';

app.use(cors({
  origin: ['https://app.jouwdomein.nl', 'https://admin.jouwdomein.nl'],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  maxAge: 86400,
}));

Laat nooit klakkeloos alle origins toe op endpoints waar cookies of tokens in het spel zijn. Dat opent de deur voor cross-site aanvallen.

Input-validatie als eerste verdedigingslinie

Veel beveiligingsproblemen beginnen bij onvertrouwde input. SQL-injectie, NoSQL-injectie, prototype pollution en command injection zijn allemaal gevolgen van slecht gevalideerde input. Valideer daarom streng op de rand van je API.

Gebruik een tool als Zod voor type-safe validatie. We behandelen dit uitgebreid in onze gids over validatie met Zod.

import { z } from 'zod';

const loginSchema = z.object({
  email: z.string().email().max(254),
  password: z.string().min(8).max(128),
});

app.post('/auth/login', (req, res, next) => {
  const result = loginSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ error: result.error.flatten() });
  }
  req.body = result.data;
  next();
}, loginHandler);

Authenticatie en autorisatie als fundament

Rate limiting beschermt niet tegen geautoriseerde misbruikers. Zorg daarom dat je API authenticatie correct implementeert. Voor REST API's is JWT of sessions de standaard, voor service-to-service communicatie gebruik je vaak API keys of mTLS.

Een paar regels om aan te houden:

  • Sla wachtwoorden alleen op als bcrypt- of argon2-hash, nooit in plaintext.
  • Gebruik korte geldigheidsduur op access tokens (bijvoorbeeld 15 minuten) en langere refresh tokens.
  • Roteer en intrek API keys makkelijk via een admin-interface.
  • Implementeer fijnmazige autorisatie per endpoint, niet alleen "ingelogd of niet".

Zie voor meer diepgang onze REST API design best practices.

Payload-grootte en timeouts

Een vaak vergeten aanval is het sturen van hele grote payloads of traag laden van verbindingen (slowloris). Beperk daarom je body-size en stel timeouts in.

app.use(express.json({ limit: '100kb' }));

const server = app.listen(3000);
server.keepAliveTimeout = 5000;
server.headersTimeout = 6000;

Voor file-uploads gebruik je specifieke endpoints met eigen limieten en bij voorkeur directe uploads naar object storage in plaats van via je Node.js-proces.

Logging, monitoring en alerting

Rate limiting en security zijn niet "set and forget". Log geweigerde requests, monitor trends en stel alerts in bij afwijkende patronen. Een plotselinge piek van 429-responses kan duiden op een aanval of een buggy client.

Meer hierover in onze observability-gids met OpenTelemetry en de production readiness checklist.

Bekijk ook de OWASP API Security Top 10 voor een volledig overzicht van de meest kritieke API-risico's en hoe je ze adresseert.

Praktische checklist voor API security

Een beknopte samenvatting om op je PR-review te plakken.

  • Rate limiting op elk publiek endpoint, strenger op auth-endpoints
  • Gedeelde rate-limit-store (Redis) bij meerdere instances
  • Helmet of vergelijkbaar voor security headers
  • Strenge CORS-configuratie, geen wildcards met credentials
  • Input-validatie met Zod of vergelijkbaar
  • Authenticatie met veilige wachtwoord-hashing en korte tokens
  • Body-size limits en server-timeouts
  • HTTPS afdwingen, nooit plaintext HTTP in productie
  • Dependencies up-to-date met npm audit en geautomatiseerde scans
  • Logging van geweigerde requests en alerts op afwijkende patronen

Veelgestelde vragen

Wat is rate limiting precies?

Rate limiting is een techniek waarmee je het aantal requests per client binnen een bepaald tijdsvenster beperkt. Zo bescherm je je API tegen misbruik, brute-force aanvallen en onverwachte verkeerspieken die je infrastructuur kunnen overbelasten.

Welke rate limiting strategie moet ik kiezen?

Voor de meeste API's werkt een token bucket of sliding window uitstekend. Fixed window is simpeler maar staat pieken toe op de grens van twee vensters. Gebruik Redis als je meerdere instances draait, anders volstaat in-memory.

Is rate limiting genoeg om mijn API te beveiligen?

Nee, rate limiting is één laag in een breder security-verhaal. Combineer het met sterke authenticatie, input-validatie, HTTPS, goede CORS-configuratie en security headers voor een robuuste verdediging tegen veelvoorkomende aanvallen.

Hoe communiceer ik limieten aan clients?

Gebruik standaard HTTP headers zoals X-RateLimit-Limit, X-RateLimit-Remaining en Retry-After. Retourneer status 429 Too Many Requests bij overschrijding. Zo kunnen clients netjes backoff-gedrag implementeren.

Moet ik rate limiting per IP of per user doen?

Idealiter allebei. Limiteer op IP voor publieke endpoints en op user ID of API key voor geauthenticeerde endpoints. Zo blokkeer je zowel anonieme misbruikers als gecompromitteerde accounts zonder legitieme gebruikers achter een NAT te raken.

Veelgestelde vragen

Klaar om digitaal te groeien?

Wij helpen Nederlandse bedrijven met webtechnologie en SEO-strategieën die écht werken. Neem vrijblijvend contact op.