Skip to content

Rate Limiting for Security — Complete Implementation Guide

DodaTech Updated 2026-06-28 5 min read

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

Rate limiting for security protects applications from brute force attacks, credential stuffing, DDoS attacks, and API abuse by restricting the rate of suspicious or high-volume requests.

What You'll Learn

By the end of this tutorial, you will implement security-focused rate limiting for login endpoints, detect and block abuse patterns, and combine rate limiting with other security measures.

Why It Matters

Security rate limiting is your first line of defense against automated attacks. Durga Antivirus Pro's backend uses aggressive rate limiting on all authentication endpoints.

Real-World Use

Durga Antivirus Pro's API blocks IPs after 5 failed login attempts in 15 minutes, preventing brute force attacks while allowing legitimate users to retry after a cooldown.

Security Rate Limiting Learning Path

flowchart LR
  A[Performance] --> B[Security]
  B --> C[Brute Force Protection]
  B --> D[DDoS Mitigation]
  B --> E[Abuse Prevention]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Brute Force Protection

Login endpoints need aggressive rate limiting to prevent password guessing. Track failed attempts separately from successful ones.

const express = require("express");
const rateLimit = require("express-rate-limit");
const app = express();

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  skipSuccessfulRequests: true,
  message: {
    error: "Too many login attempts. Account locked for 15 minutes."
  },
  keyGenerator: (req) => {
    return req.body?.username
      ? `login:${req.body.username}`
      : `login:${req.ip}`;
  }
});

app.post("/login", loginLimiter, (req, res) => {
  if (req.body.password === "wrong") {
    return res.status(401).json({ error: "Invalid credentials" });
  }
  res.json({ token: "success" });
});

app.listen(3000);

Expected behavior: 5 failed login attempts for the same username trigger a 15-minute lockout. Successful logins reset the counter.

DDoS Mitigation at Application Level

While infrastructure handles large DDoS attacks, application-level rate limiting provides defense against application-layer attacks.

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

const ddosProtection = rateLimit({
  windowMs: 1000,
  max: 20,
  message: { error: "Request rate too high" },
  keyGenerator: (req) => req.ip,
  handler: (req, res) => {
    console.warn(`DDoS suspected from ${req.ip}: ${req.path}`);
    res.status(429).json({ error: "Request rate too high" });
  }
});

app.use(ddosProtection);

Expected behavior: More than 20 requests per second from a single IP triggers warnings and blocks additional requests.

Abuse Pattern Detection

Beyond simple rate limiting, detect and block specific abuse patterns like scraping, content theft, and automated account creation.

class AbuseDetector {
  constructor(redis) {
    this.redis = redis;
    this.patterns = new Map();
  }

  async checkAbuse(req) {
    const score = await this.calculateAbuseScore(req);

    if (score > 50) {
      await this.redis.incr(`abuse:blocked:${req.ip}`);
      await this.redis.expire(`abuse:blocked:${req.ip}`, 3600);
      return { blocked: true, score };
    }

    return { blocked: false, score };
  }

  async calculateAbuseScore(req) {
    let score = 0;

    // Rapid sequential access
    const rapidAccess = await this.redis.get(`rapid:${req.ip}`);
    if (rapidAccess && parseInt(rapidAccess) > 50) score += 20;

    // Accessing many different resources quickly
    const uniqueResources = await this.redis.scard(`resources:${req.ip}`);
    if (uniqueResources > 100) score += 15;

    // Repeated 404s (probe for vulnerabilities)
    if (req.statusCode === 404) {
      const notFound = await this.redis.incr(`404:${req.ip}`);
      if (notFound > 20) score += 25;
    }

    // Missing or suspicious user agent
    if (!req.headers["user-agent"] || req.headers["user-agent"].includes("curl")) {
      score += 5;
    }

    return score;
  }

  middleware() {
    return async (req, res, next) => {
      const result = await this.checkAbuse(req);
      if (result.blocked) {
        return res.status(429).json({
          error: "Access denied",
          reason: "Abuse pattern detected",
          score: result.score
        });
      }
      next();
    };
  }
}

Common Mistakes

  1. Rate limiting only by IP -- Attackers use botnets with many IPs. Combine with user-based and behavior-based limiting.

  2. Revealing whether an account exists -- "Invalid username" vs "Invalid password" reveals valid usernames. Use generic error messages.

  3. Not rate limiting registration endpoints -- Automated account creation can fill your database with fake accounts.

  4. Setting limits too high for security endpoints -- Login should allow 3-5 attempts per 15 minutes. Higher limits invite brute force.

  5. Forgetting to rate limit password reset endpoints -- Password reset is equally vulnerable to abuse as login.

Practice Questions

  1. Why should security rate limiting use skipSuccessfulRequests? Successful logins indicate legitimate users. Counting them would lock out users who occasionally forget passwords.

  2. How do you protect against credential stuffing (trying many usernames with one password)? Rate limit by IP for login attempts, regardless of username. This catches automated tools that try many accounts.

  3. What is the difference between rate limiting and DDoS protection? Rate limiting controls request frequency per client. DDoS protection handles massive traffic from many sources.

  4. Challenge: Implement a rate limiter that gradually increases penalties for repeat offenders.

function graduatedPenalty(req) {
  const offenses = getOffenseCount(req.ip);
  const baseLimit = 100;
  const penalty = Math.pow(2, offenses - 1); // 1, 2, 4, 8, 16
  return Math.max(1, Math.floor(baseLimit / penalty));
}

FAQ

Can rate limiting replace a WAF?

No. Rate limiting is one layer of defense. A Web Application Firewall provides broader protection including SQL injection and XSS filtering.

How do I rate limit API key usage securely?

Combine per-key rate limiting with per-IP rate limiting. This prevents stolen keys from being used from many IPs.

Should I block or delay suspicious requests?

Delaying (slowdown) is often better than blocking. Attackers cannot distinguish between a slow response and a blocked one.

How do I handle rate limiting for legitimate web crawlers?

Whitelist known crawler IPs and user agents. Provide a separate, higher rate limit for verified crawlers.

What is the most common security rate limiting mistake?

Setting limits too high. Most applications use limits that are 10x higher than necessary, leaving them vulnerable to brute force.

Mini Project

Build a security-focused rate limiting system with login protection, abuse detection, graduated penalties, and IP reputation tracking.

class SecurityRateLimiter {
  constructor(redis) {
    this.redis = redis;
  }

  async checkLogin(username, ip) {
    const userKey = `sec:login:user:${username}`;
    const ipKey = `sec:login:ip:${ip}`;
    const maxAttempts = 5;
    const windowMs = 900000;

    const [userAttempts, ipAttempts] = await Promise.all([
      this.redis.incr(userKey),
      this.redis.incr(ipKey)
    ]);

    if (userAttempts === 1) await this.redis.expire(userKey, Math.ceil(windowMs / 1000));
    if (ipAttempts === 1) await this.redis.expire(ipKey, Math.ceil(windowMs / 1000));

    if (userAttempts > maxAttempts) {
      return { blocked: true, reason: "Account temporarily locked", retryAfter: windowMs / 1000 };
    }

    if (ipAttempts > maxAttempts * 3) {
      return { blocked: true, reason: "IP temporarily blocked", retryAfter: windowMs / 1000 };
    }

    return { blocked: false };
  }

  async recordSuccessfulLogin(username, ip) {
    await this.redis.del(`sec:login:user:${username}`);
  }

  middleware() {
    return async (req, res, next) => {
      if (req.path === "/login" && req.method === "POST") {
        const result = await this.checkLogin(req.body?.username, req.ip);
        if (result.blocked) {
          return res.status(429).json(result);
        }
      }
      next();
    };
  }
}

What's Next

Now that you understand rate limiting for security, apply everything in the rate limiting comprehensive project. Then explore retry strategies for resilient systems.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro