Every endpoint that costs you money, sends something, or can be scripted at scale needs an explicit abuse budget — "we'll add rate limiting later" is how free tiers get drained overnight.
What needs a limit
- Rate-limit every authentication endpoint (login, signup, password reset, MFA verification) independently — these are the most commonly brute-forced surfaces in any app.
- Rate-limit anything that sends an external notification (email, SMS, push) per-recipient, not just per-sender — otherwise a single attacker can use your signup form to spam a third party's inbox.
- Rate-limit any endpoint that triggers a paid third-party call (an LLM API, an SMS provider, a payment processor) more conservatively than a "free" endpoint — the cost of abuse there is direct and immediate, not just degraded performance.
Choosing limits
- Set limits per identity axis independently: per-IP AND per-account AND, where relevant, per-API-key — a limit on only one axis is trivially bypassed by rotating the other (many accounts from one IP, or one account across many IPs/proxies).
- Base initial limits on realistic legitimate usage patterns plus headroom, not a round number picked arbitrarily — too tight and you block real users; too loose and the limit does nothing. Expect to tune it after observing real traffic.
- Use a sliding window or token-bucket algorithm rather than a naive fixed-window counter — fixed windows allow a burst of up to 2x the limit right at the window boundary (e.g. 100 requests in the last second of one window plus 100 in the first second of the next).
Responding to limit breaches
- Return a proper
429 Too Many Requests status with a Retry-After header, not a generic error — well-behaved clients and legitimate integrations use this to back off correctly instead of retrying in a tight loop.
- Never reveal specifics that help an attacker calibrate (exact remaining quota, exact reset time to the millisecond) on unauthenticated endpoints — enough to be usable, not enough to be exploitable.
Beyond simple rate limits
- Add CAPTCHA or a proof-of-work challenge only after a suspicious pattern is detected (repeated failures, known bad IP ranges), not on every single request — gate it behind signal, don't tax every legitimate user by default.
- Detect and throttle patterns, not just raw request counts: many accounts created from the same IP in a short window, many failed logins across many different accounts (credential stuffing), or many requests with sequential/enumerated IDs.
Implementation notes
- Rate limit state must be shared across all server instances (a shared store like Redis, or your database), never in-process memory alone — in-memory counters reset per instance and are useless the moment you run more than one server process.
- Fail safe under rate-limiter outage: decide deliberately whether to fail open (allow requests, log the limiter failure) or fail closed (block requests) per endpoint sensitivity — an unauthenticated payment endpoint should fail closed; a low-risk read endpoint can usually fail open.
Comments (0)
Sign in to comment. Sign in
No comments yet. Be the first to add one.