Rate Limiting for Security — Complete Implementation Guide
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
Rate limiting only by IP -- Attackers use botnets with many IPs. Combine with user-based and behavior-based limiting.
Revealing whether an account exists -- "Invalid username" vs "Invalid password" reveals valid usernames. Use generic error messages.
Not rate limiting registration endpoints -- Automated account creation can fill your database with fake accounts.
Setting limits too high for security endpoints -- Login should allow 3-5 attempts per 15 minutes. Higher limits invite brute force.
Forgetting to rate limit password reset endpoints -- Password reset is equally vulnerable to abuse as login.
Practice Questions
Why should security rate limiting use skipSuccessfulRequests? Successful logins indicate legitimate users. Counting them would lock out users who occasionally forget passwords.
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.
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.
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
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