Skip to content

Rate Limiting for Security: Preventing Abuse and Brute Force Attacks

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Rate Limiting for Security: Preventing Abuse and Brute Force Attacks. We cover key concepts, practical examples, and best practices to help you master this topic.

Rate limiting controls how many requests a client can make within a specific time window. As a security control, it protects against brute force login attempts, credential stuffing, API key abuse, DDoS attacks, and web scraping by limiting the rate of requests from a single source.

flowchart TB
    Attacker[Attacker] -->|1000 requests/sec| RateLimiter[Rate Limiter]
    RateLimiter -->|Allow 100/min| Normal[Legitimate User]
    RateLimiter -->|Block 429| Blocked[Blocked Client]
    RateLimiter -->|Distributed| Redis[(Redis Counter)]
    
    subgraph Algorithms
        TB[Token Bucket]
        SW[Sliding Window]
        FP[Fixed Window]
    end
    RateLimiter --> TB
    RateLimiter --> SW
    RateLimiter --> FP

What You'll Learn

  • Rate limiting algorithms for security (token bucket, Sliding Window, fixed window)
  • Per-IP, per-user, and per-endpoint rate limiting
  • Brute force protection with rate limiting
  • Distributed rate limiting with Redis

Why It Matters

Without rate limiting, a single attacker can try millions of passwords against your login endpoint in minutes. Rate limiting makes brute force attacks economically infeasible by limiting attempts to a few per minute.

Real-World Use

A payment API limits each API key to 1000 requests per hour. A compromised API key can only cause limited damage before rate limiting kicks in. Additionally, the login endpoint is limited to 5 attempts per IP per 15 minutes, preventing credential stuffing attacks.

Rate Limiting for Security

Login Rate Limiting (Brute Force Protection)

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

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: { error: 'Too many login attempts. Please try again later.' },
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => {
    // Rate limit by IP + username to prevent distributed brute force
    return `${req.ip}:${req.body.username || 'unknown'}`;
  },
  handler: (req, res) => {
    // Log the rate limit event for security monitoring
    console.warn(`Rate limit hit: login from ${req.ip} for user ${req.body.username}`);
    res.status(429).json({ error: 'Too many login attempts.' });
  }
});

app.post('/api/login', loginLimiter, async (req, res) => {
  // Login logic
});

Expected output:

After 5 failed login attempts for the same user from any IP (or same IP for any user), subsequent attempts return 429.

API Key Rate Limiting

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

const apiLimiter = rateLimit({
  windowMs: 60 * 60 * 1000,
  max: 1000,
  keyGenerator: (req) => req.headers['x-api-key'] || req.ip,
  handler: (req, res) => {
    res.status(429).json({
      error: 'Rate limit exceeded',
      retryAfter: res.getHeader('Retry-After')
    });
  }
});

const strictEndpointLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 30,
  keyGenerator: (req) => req.headers['x-api-key'] || req.ip
});

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

Expected output:

1000 requests per API key per hour globally. 30 requests per minute for the search endpoint specifically.

Distributed Rate Limiting with Redis (Sliding Window)

const redis = require('redis');
const client = redis.createClient();

async function slidingWindowRateLimit(key, maxRequests, windowSeconds) {
  const now = Date.now();
  const windowStart = now - windowSeconds * 1000;

  const multi = client.multi();
  multi.zRemRangeByScore(key, 0, windowStart);
  multi.zCard(key);
  multi.zAdd(key, now, `${now}-${Math.random()}`);
  multi.expire(key, windowSeconds);

  const [, count] = await multi.exec();

  if (count >= maxRequests) {
    return { allowed: false, remaining: 0 };
  }

  return { allowed: true, remaining: maxRequests - count - 1 };
}

// Middleware
async function redisRateLimit(req, res, next) {
  const key = `ratelimit:${req.ip}:${req.path}`;
  const result = await slidingWindowRateLimit(key, 10, 60);

  res.set('X-RateLimit-Remaining', result.remaining);

  if (!result.allowed) {
    return res.status(429).json({ error: 'Rate limit exceeded' });
  }
  next();
}

Expected output:

Sliding window tracks request timestamps in Redis sorted set. Old entries are removed, count is precise. Limits to 10 requests per minute per IP per path.

Common Mistakes

  • Rate limiting only by IP — attackers using botnets have many IPs. Combine IP with user ID or API key.
  • Not rate limiting expensive endpoints differently — search and export endpoints should have stricter limits than simple reads.
  • Using fixed window instead of sliding window — fixed window allows burst at window boundaries (e.g., 100 requests in the last second of a window).
  • Not returning Retry-After header — clients need to know when they can retry.
  • Rate limiting before authentication — unauthenticated users should have stricter limits than authenticated ones.

Practice Questions

  1. How does rate limiting prevent brute force attacks?
  2. What is the difference between fixed window and sliding window rate limiting?
  3. Why should you rate limit by more than just IP address?
  4. How does the Retry-After header help clients?
  5. What is the token bucket algorithm?

Challenge

Design a rate limiting Strategy for a social media API. Implement: (1) global rate limit per API key (1000/hour), (2) login rate limit (5/15min per IP+user), (3) post creation limit (10/hour per user), (4) search endpoint limit (30/min per key). Use Redis for distributed counting.

FAQ

What is rate limiting?

Rate limiting restricts the number of requests a client can make in a given time window. It prevents abuse, brute force attacks, and ensures fair resource allocation.

What is the difference between rate limiting and throttling?

Rate limiting blocks requests that exceed the limit. Throttling slows down requests (queues them) without necessarily blocking.

Should I rate limit by IP or user?

Both. Rate limit by IP to prevent distributed attacks, and by user (or API key) to prevent authenticated abuse. Use separate limits for each.

What HTTP status code should I return for rate limiting?

Use 429 Too Many Requests. Include a Retry-After header indicating when the client can retry.

How do I implement rate limiting in a distributed system?

Use a centralized counter in Redis. All instances read and write to the same Redis key, ensuring consistent limits across instances.

Mini Project

Build a rate-limited API with three tiers: anonymous (10 req/min), authenticated (100 req/min), and premium (1000 req/min). Implement Redis-based sliding window. Add rate limit headers to all responses. Write a load test that verifies each tier's limits.

What's Next

Continue to Input Validation to learn comprehensive input validation and sanitization strategies.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro