Rate limiting and defending against brute force and credential stuffing
Attackers automate guessing. Rate limits, smart lockouts and multi-factor authentication make that guessing slow, costly and noisy.

Anything an attacker can try automatically, they will try thousands of times: passwords, one-time codes, coupon codes, reset tokens, API endpoints. Rate limiting controls how often a client can do something, so abuse becomes slow and expensive while normal users are not affected. It also protects against accidental overload and runaway scripts.
This is a defensive guide to designing limits and related controls.
The threats
- Brute force: many guesses against one account (for example all passwords or all 6-digit codes).
- Password spraying: a few common passwords tried against many accounts, staying under per-account limits.
- Credential stuffing: using username and password pairs leaked from other sites, since many people reuse passwords. Often done with large botnets and many IP addresses.
- Enumeration: discovering which emails or usernames exist from different responses.
- Abuse of expensive endpoints: search, exports, password reset emails, SMS sending (which costs money), and heavy API calls.
- Denial of service from excessive requests.
Rate limiting algorithms
- Fixed window: allow N requests per time window (for example 100 per minute). Simple, but a burst at the window boundary can allow up to double the limit.
- Sliding window: counts requests over a rolling period, smoother and fairer.
- Token bucket: a bucket holds tokens that refill at a steady rate. Each request uses a token. It allows short bursts while enforcing an average rate, and it is widely used.
- Leaky bucket: requests are processed at a constant rate, smoothing bursts.
- Concurrency limits: cap the number of requests in flight at one time.
A shared store such as Redis is commonly used to keep counters consistent across several servers. Make the update atomic (for example with a Lua script or atomic increment with expiry) so concurrent requests cannot slip past.
What to limit by
No single key is enough. Combine several:
- IP address: simple, but many users can share one IP (offices, mobile carriers), and attackers can rotate IPs. Be careful to use the real client IP from a trusted proxy header and not a value the client can forge.
- Account or username: limits guessing against one target even from many IPs.
- API key or user ID for authenticated APIs.
- Device or session fingerprints as an additional signal.
- Endpoint-specific limits: stricter for login, password reset, OTP verification and SMS sending than for harmless reads.
Responding when the limit is hit
- Return HTTP 429 Too Many Requests with a
Retry-Afterheader, and a clear message. - Use progressive delays (each failed attempt waits longer) or temporary lockouts of minutes rather than long permanent ones.
- Beware that hard account lockouts let an attacker lock real users out on purpose (a denial of service). Prefer soft measures: slowing down, requiring a CAPTCHA, or requiring extra verification from unknown devices, while allowing the known device or a verified reset path.
- Do not reveal too much detail about limits to attackers. Keep rules and thresholds sensible.
Login-specific protections
- Generic error messages: "Incorrect email or password", the same for unknown users and wrong passwords. Keep response time similar, since timing differences can also leak which accounts exist.
- Apply limits on both IP and account, and monitor for the pattern of one IP trying many usernames.
- Multi-factor authentication (MFA) is the strongest defence against credential stuffing, because stolen passwords alone no longer work. Prefer authenticator apps, security keys or passkeys over SMS where possible.
- Check passwords against known breached lists when users set them, and alert or force a reset for accounts using exposed passwords.
- Use strong password hashing (see the password storage article) so a database leak is less useful.
- Notify users of new-device logins and suspicious attempts.
- Consider risk-based authentication: ask for extra proof when the device, location or behaviour is unusual.
CAPTCHA and bot management
- A CAPTCHA or challenge can slow automated attacks, best used after suspicious behaviour rather than for everyone, because it hurts usability and accessibility.
- Bot management services (often part of a CDN or WAF) use reputation, behaviour and fingerprints to filter traffic, and can block known bad networks.
- No single challenge is perfect, since attackers can use solving services, so layer controls.
OTP and reset token protection
- Limit verification attempts for one-time codes. A 6-digit code has only a million possibilities, so without limits it can be brute forced.
- Make codes and reset tokens short-lived, single-use and random.
- Rate limit sending of SMS and email to prevent cost abuse and harassment of victims.
- Invalidate previous codes when a new one is issued.
Protect expensive and sensitive endpoints
- Stricter limits on search, exports, file processing and anything calling a paid service.
- Request size and timeout limits, pagination caps and query complexity limits.
- Use quotas per plan or user for APIs (daily or monthly), plus a short-term rate limit.
- Queue heavy work and process it asynchronously.
Put limits in the right layer
- Edge (CDN, WAF, API gateway): blunt, early filtering of floods and obvious bots before they reach your servers.
- Application: fine-grained rules that understand users, accounts and business logic (for example per-account login attempts).
- Both layers together work best.
Monitoring and detection
- Log failed logins, lockouts and rate-limit hits with IP, account, user agent and endpoint (without logging passwords).
- Alert on spikes: failed-login rate, many accounts from one IP or the reverse, new-device logins at unusual volume, high 429 counts.
- Review logs after incidents and tune thresholds. Too strict harms real users, too loose does nothing. Test with realistic traffic.
Common mistakes
- Trusting the
X-Forwarded-Forheader without checking that it comes from your own proxy, so attackers can fake their IP and bypass limits. - Limiting only by IP, or only by account.
- Counters that are not atomic or are kept in the memory of a single server behind a load balancer.
- Different error messages that reveal valid accounts.
- Permanent hard lockouts that attackers can abuse.
- No limit on OTP verification or password reset requests.
- No monitoring, so attacks go unnoticed.
Checklist
- Limit login, reset, OTP and signup endpoints by IP and account.
- Use token bucket or sliding window with an atomic shared store.
- Return 429 with
Retry-After, and use soft, temporary penalties. - Offer MFA, check breached passwords, notify on new devices.
- Add CAPTCHA or challenges after suspicious activity.
- Limit OTP attempts and rate limit SMS and email sending.
- Protect expensive endpoints with quotas.
- Log, alert and tune.
Takeaway: assume automated guessing will happen. Combine rate limits by IP and account, MFA, breached-password checks, soft lockouts, bot controls and monitoring, so attacks are slow, noisy and unprofitable.
Web security series, Part 3 (accounts and defence): 5 of 5. Previous: Dependency and supply chain security: protecting your app from risky packages.


