Rate Limiting, Quotas & Abuse Protection · Where and What to Enforce
Application-Level Limits
Business-aware limits implemented in your services.
Application limits encode domain rules: one OTP per phone per 60 s and 5 per hour, 10 failed logins per account per 15 min, 3 password resets per email per day, N exports per tenant per day, invitations per hour. Implement them as reusable middleware/decorators/dependencies with clear keys, return Problem Details 429 with Retry-After, and log limit hits as security signals.
Common business limits
| Action | Typical limit | Key |
|---|---|---|
| Send OTP / SMS | 1/min, 5/hour | phone number + IP |
| Failed logins | 5–10 per 15 min | account + IP |
| Password reset | 3/day | |
| Exports | 10/day | tenant |
| Invitations | 50/hour | account |
def limit(name: str, capacity: int, per_seconds: int, key_fn):
async def dep(request: Request):
key = f"{name}:{await key_fn(request)}"
ok, _, retry = token_bucket(key, capacity, capacity / per_seconds)
if not ok:
raise HTTPException(429, "Too many attempts", headers={"Retry-After": str(retry)})
return Depends(dep)
@router.post("/otp", dependencies=[limit("otp-phone", 5, 3600, lambda r: phone_from_body(r))])
async def send_otp(body: OtpIn): ...
export const limitBy = (name: string, capacity: number, perSec: number, keyFn: (req: Request) => string) =>
async (req: Request, res: Response, next: NextFunction) => {
const { ok, retryAfter } = await limit(`${name}:${keyFn(req)}`, capacity, perSec);
if (!ok) return res.status(429).set("Retry-After", String(retryAfter)).json({ title: "Too many attempts" });
next();
};
app.post("/password-resets", limitBy("reset-email", 3, 3 / 86400, (r) => String(r.body.email).toLowerCase()), resetHandler);
@Component
class LoginLimitInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws IOException {
String key = "login:" + req.getRemoteAddr();
if (!limiter.tryConsume(key, 10, Duration.ofMinutes(15))) {
res.setStatus(429);
res.setHeader("Retry-After", "900");
return false;
}
return true;
}
}
Common pitfalls
- Counting successful logins toward lockout limits.
- Limits keyed only on IP for OTP (SMS pumping fraud).
- Silent limits that look like bugs to users.
Key points
- Encode domain rules
- Reusable middleware
- Log limit hits as signals
Further reading: OWASP Authentication Cheat Sheet
Practice questions
What is SMS pumping fraud and how do rate limits help?
Answer
Attackers trigger OTP/SMS sends to premium numbers they profit from; per-phone, per-IP and per-country limits (plus anomaly detection) cap the damage.
Should successful logins count toward failed-login limits?
Answer
No — count failures, and consider resetting counters on success, so legitimate users aren't locked out.
Open in the app to mark lessons complete, take notes and track your progress.