If your login, password-reset, or verification endpoint accepts unlimited attempts, an attacker can try thousands of passwords or codes per minute until one works. Rate limiting caps how many attempts an IP or account can make in a window, which turns an easy brute-force into an impractical one. Add it to every authentication route.
Why it's a problem
Without a limit, a six-digit reset code has only a million possibilities and can be exhausted quickly, and weak passwords fall to a dictionary in seconds. The endpoint works normally for real users, so the missing limit is invisible until someone points an automated tool at it.
The pattern
// unlimited attempts
app.post("/login", async (req, res) => {
const ok = await checkPassword(req.body);
res.json({ ok });
});The fix
import rateLimit from "express-rate-limit";
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 min
max: 10, // per IP
});
app.post("/login", loginLimiter, async (req, res) => {
const ok = await checkPassword(req.body);
res.json({ ok });
});Why AI tools write this
Generating a working login route answers the request as asked; rate limiting was not part of it. The endpoint authenticates correctly in testing, so the absence of a limit does not surface until it is attacked.
The quick fix
- Add rate limiting to login, password-reset, and verification endpoints.
- Limit by IP and, where possible, by account.
- Consider a short lockout or increasing delay after repeated failures.
Common questions
Should I rate limit by IP or by account?
Both, where possible. IP-based limiting is easy to implement but can be bypassed with rotating proxies. Account-based limiting or a lockout after N failures protects accounts regardless of the IP used.
What is a reasonable rate limit for a login endpoint?
Five to ten attempts per IP per 15-minute window is a common starting point. After that, a short lockout or a CAPTCHA. Adjust based on your expected legitimate traffic patterns.
Does rate limiting alone protect against credential stuffing?
Not fully. Credential stuffing uses many IPs, so per-IP limits slow but do not stop it. Add anomaly detection, breach password checking, and consider a CAPTCHA for high-failure-rate IPs.