Rate Limiting and DDoS Protection: Defending Against Abuse and Attacks

Rate Limiting and DDoS Protection: Defending Against Abuse and Attacks

Whitespots Team ·
rate-limiting
ddos
api
protection

Introduction

Rate limiting and DDoS protection are essential for preventing abuse, ensuring fair resource usage, and protecting your infrastructure from attacks. Without proper rate limiting, APIs and applications are vulnerable to brute force attacks, resource exhaustion, and denial of service. This guide covers implementation strategies with practical examples.

Common Attack Patterns

  1. Brute force login attempts
  2. API abuse and scraping
  3. DDoS attacks
  4. Resource exhaustion
  5. Credential stuffing
  6. Application-layer attacks
  7. Bot traffic
  8. Inventory denial attacks

Rate Limiting Strategies

Fixed Window Rate Limiting

javascript
// Express.js - Simple fixed window rate limiter const rateLimit = require('express-rate-limit'); const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // 5 requests per window message: 'Too many login attempts, please try again later', standardHeaders: true, legacyHeaders: false, handler: (req, res) => { res.status(429).json({ error: 'Too many requests', retryAfter: req.rateLimit.resetTime }); } }); app.post('/login', loginLimiter, async (req, res) => { // Login logic });

Sliding Window Rate Limiting

javascript
// Redis-based sliding window const Redis = require('ioredis'); const redis = new Redis(); async function slidingWindowRateLimit(userId, limit, window) { const key = `rate:${userId}`; const now = Date.now(); const windowStart = now - window; // Remove old entries await redis.zremrangebyscore(key, 0, windowStart); // Count requests in window const count = await redis.zcard(key); if (count >= limit) { return { allowed: false, remaining: 0 }; } // Add current request await redis.zadd(key, now, `${now}-${Math.random()}`); await redis.expire(key, Math.ceil(window / 1000)); return { allowed: true, remaining: limit - count - 1 }; } // Middleware async function rateLimitMiddleware(req, res, next) { const userId = req.user?.id || req.ip; const result = await slidingWindowRateLimit(userId, 100, 60000); // 100 req/min res.setHeader('X-RateLimit-Limit', 100); res.setHeader('X-RateLimit-Remaining', result.remaining); if (!result.allowed) { return res.status(429).json({ error: 'Rate limit exceeded' }); } next(); }

Token Bucket Algorithm

javascript
// Token bucket implementation class TokenBucket { constructor(capacity, fillRate) { this.capacity = capacity; this.tokens = capacity; this.fillRate = fillRate; // tokens per second this.lastFill = Date.now(); } refill() { const now = Date.now(); const elapsed = (now - this.lastFill) / 1000; const tokensToAdd = elapsed * this.fillRate; this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd); this.lastFill = now; } consume(tokens = 1) { this.refill(); if (this.tokens >= tokens) { this.tokens -= tokens; return true; } return false; } getTokens() { this.refill(); return this.tokens; } } // Per-user buckets const buckets = new Map(); function getOrCreateBucket(userId) { if (!buckets.has(userId)) { // 100 tokens capacity, refill at 10 tokens/second buckets.set(userId, new TokenBucket(100, 10)); } return buckets.get(userId); } app.use((req, res, next) => { const userId = req.user?.id || req.ip; const bucket = getOrCreateBucket(userId); if (bucket.consume(1)) { res.setHeader('X-RateLimit-Remaining', Math.floor(bucket.getTokens())); next(); } else { res.status(429).json({ error: 'Rate limit exceeded', retryAfter: Math.ceil((1 - bucket.getTokens()) / bucket.fillRate) }); } });

Different Limits for Different Endpoints

javascript
// Tiered rate limiting const createRateLimiter = (windowMs, max) => rateLimit({ windowMs, max, standardHeaders: true, legacyHeaders: false }); // Strict limits for auth endpoints const authLimiter = createRateLimiter(15 * 60 * 1000, 5); // 5 per 15min // Moderate limits for API endpoints const apiLimiter = createRateLimiter(60 * 1000, 100); // 100 per minute // Relaxed limits for public endpoints const publicLimiter = createRateLimiter(60 * 1000, 1000); // 1000 per minute app.post('/login', authLimiter, loginHandler); app.post('/register', authLimiter, registerHandler); app.get('/api/users', apiLimiter, getUsersHandler); app.get('/public/status', publicLimiter, statusHandler);

IP-Based and User-Based Limiting

javascript
// Combined IP and user-based rate limiting const rateLimit = require('express-rate-limit'); const RedisStore = require('rate-limit-redis'); const Redis = require('ioredis'); const redis = new Redis(); // IP-based limiter (for unauthenticated requests) const ipLimiter = rateLimit({ store: new RedisStore({ client: redis, prefix: 'rl:ip:' }), windowMs: 60 * 1000, max: 60, keyGenerator: (req) => req.ip }); // User-based limiter (for authenticated requests) const userLimiter = rateLimit({ store: new RedisStore({ client: redis, prefix: 'rl:user:' }), windowMs: 60 * 1000, max: 200, keyGenerator: (req) => req.user?.id || req.ip }); // Apply appropriate limiter app.use((req, res, next) => { if (req.user) { userLimiter(req, res, next); } else { ipLimiter(req, res, next); } });

Nginx Rate Limiting

nginx
# Nginx rate limiting configuration http { # Define rate limit zones limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=auth:10m rate=1r/m; limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m; # Connection limits limit_conn_zone $binary_remote_addr zone=addr:10m; server { listen 443 ssl http2; server_name api.example.com; # General rate limit limit_req zone=general burst=20 nodelay; limit_conn addr 10; # Auth endpoints - strict limits location /auth { limit_req zone=auth burst=5 nodelay; proxy_pass http://backend; } # API endpoints location /api { limit_req zone=api burst=50 nodelay; proxy_pass http://backend; } # Return 429 for rate limit error_page 429 = @ratelimit; location @ratelimit { add_header Content-Type application/json always; return 429 '{"error": "Too many requests"}'; } } }

DDoS Protection Strategies

Application-Layer Protection

javascript
// Express.js - DDoS protection middleware const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const slowDown = require('express-slow-down'); // Helmet for security headers app.use(helmet()); // Rate limiting const limiter = rateLimit({ windowMs: 1 * 60 * 1000, max: 100 }); app.use(limiter); // Slow down responses for repeated requests const speedLimiter = slowDown({ windowMs: 15 * 60 * 1000, delayAfter: 50, delayMs: 500 }); app.use(speedLimiter); // Request size limits app.use(express.json({ limit: '10kb' })); app.use(express.urlencoded({ extended: true, limit: '10kb' })); // Timeout middleware const timeout = require('connect-timeout'); app.use(timeout('5s')); // Abort long-running requests app.use((req, res, next) => { if (!req.timedout) next(); });

Cloudflare Integration

javascript
// Verify Cloudflare requests function verifyCloudflare(req, res, next) { const cfConnectingIp = req.headers['cf-connecting-ip']; const cfRay = req.headers['cf-ray']; if (!cfConnectingIp || !cfRay) { return res.status(403).json({ error: 'Direct access not allowed' }); } // Use Cloudflare's IP as the real IP req.realIp = cfConnectingIp; next(); } app.use(verifyCloudflare); // Cloudflare rate limiting (via API) async function updateCloudflareRateLimit(zoneId, ruleId, config) { const response = await fetch( `https://api.cloudflare.com/client/v4/zones/${zoneId}/rate_limits/${ruleId}`, { method: 'PUT', headers: { 'X-Auth-Email': process.env.CF_EMAIL, 'X-Auth-Key': process.env.CF_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ threshold: 100, period: 60, action: { mode: 'challenge', // or 'ban', 'block' timeout: 86400 }, ...config }) } ); return response.json(); }

Bot Detection and Blocking

javascript
// Bot detection middleware const isbot = require('isbot'); function botDetection(req, res, next) { const userAgent = req.headers['user-agent']; // Block known bots if (isbot(userAgent)) { return res.status(403).json({ error: 'Bot access denied' }); } // Check for bot patterns const botPatterns = [ /curl/i, /wget/i, /python/i, /scrapy/i, /bot/i, /crawler/i ]; if (botPatterns.some(pattern => pattern.test(userAgent))) { return res.status(403).json({ error: 'Automated access denied' }); } next(); } // Apply to sensitive endpoints app.use('/api', botDetection);

CAPTCHA Integration

javascript
// reCAPTCHA verification const axios = require('axios'); async function verifyCaptcha(token) { const response = await axios.post( 'https://www.google.com/recaptcha/api/siteverify', null, { params: { secret: process.env.RECAPTCHA_SECRET, response: token } } ); return response.data.success && response.data.score >= 0.5; } app.post('/login', async (req, res) => { const { username, password, captchaToken } = req.body; // Verify CAPTCHA for suspicious requests const requestCount = await getRecentFailedLogins(req.ip); if (requestCount > 3) { const isHuman = await verifyCaptcha(captchaToken); if (!isHuman) { return res.status(403).json({ error: 'CAPTCHA verification failed' }); } } // Proceed with authentication const user = await authenticateUser(username, password); if (!user) { await recordFailedLogin(req.ip); return res.status(401).json({ error: 'Invalid credentials' }); } res.json({ success: true }); });

Monitoring and Alerting

javascript
// Rate limit monitoring const prometheus = require('prom-client'); // Metrics const rateLimitCounter = new prometheus.Counter({ name: 'rate_limit_exceeded_total', help: 'Total number of rate limit violations', labelNames: ['endpoint', 'ip'] }); const requestDuration = new prometheus.Histogram({ name: 'http_request_duration_seconds', help: 'Request duration', labelNames: ['method', 'route', 'status'] }); // Middleware app.use((req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = (Date.now() - start) / 1000; requestDuration.labels(req.method, req.route?.path || req.path, res.statusCode).observe(duration); if (res.statusCode === 429) { rateLimitCounter.labels(req.path, req.ip).inc(); // Alert if threshold exceeded if (rateLimitCounter.get().values.length > 100) { sendAlert('High rate of rate limit violations detected'); } } }); next(); }); // Metrics endpoint app.get('/metrics', async (req, res) => { res.set('Content-Type', prometheus.register.contentType); res.end(await prometheus.register.metrics()); });

Rate Limiting Best Practices Checklist

  • ✅ Implement multiple rate limiting tiers
  • ✅ Use Redis for distributed rate limiting
  • ✅ Return proper 429 status codes
  • ✅ Include Retry-After headers
  • ✅ Different limits for different endpoints
  • ✅ Stricter limits for authentication
  • ✅ Combine IP and user-based limiting
  • ✅ Monitor rate limit violations
  • ✅ Implement progressive delays
  • ✅ Use CAPTCHA for suspicious activity
  • ✅ Block known bots and scrapers
  • ✅ Set request size limits
  • ✅ Implement request timeouts
  • ✅ Use CDN for DDoS protection
  • ✅ Log and analyze traffic patterns

Conclusion

Rate limiting and DDoS protection are critical defenses against abuse and attacks. By implementing tiered rate limiting, bot detection, and proper monitoring, you protect your infrastructure while maintaining service availability for legitimate users.

These protections should be continuously monitored and adjusted based on traffic patterns and emerging threats. For comprehensive DDoS protection strategies and rate limiting architecture reviews, contact the Whitespots team for expert consultation.