DEV Community

StarkMan
StarkMan

Posted on

Rate Limiting Without the Guessing: Engineering API Abuse Controls That Hold

Rate Limiting Without the Guessing: Engineering API Abuse Controls That Hold

Rate limiting gets treated as a configuration checkbox. It is not. A limit that is set once and never revisited will either break real users or protect nothing.
OWASP's 2023 API list moved this problem into the main body of the document. API4, Unrestricted Resource Consumption, covers request counts plus CPU, memory, storage and third-party spend. The older framing assumed a fixed number of calls per minute was the whole story. It is not.

The three places abuse actually shows up

Most teams discover abuse in one of three shapes.

  • Credential stuffing: many accounts, low request volume per account, high failure ratio.
  • Enumeration: walking an identifier space to find the objects that exist.
  • Economic abuse: expensive operations (search, export, report generation) called at a rate that is polite per client and ruinous in aggregate. A single global requests-per-minute rule catches none of the three cleanly, because each one has a different cost shape.

Count the right thing

The counter matters more than the threshold. Counting IP addresses fails against mobile carrier NAT and against distributed sources. Counting API keys fails when a key is shared across a partner's infrastructure. Useful keys tend to be a mix:

  • a subject key (authenticated user or client id) for fairness
  • a resource key (object, tenant, endpoint) for fairness across tenants
  • an operation-cost key where a single call has measurable expense Microsoft's API Management material makes the same practical point with its distinction between a rate limit (short window, protects availability) and a quota (long window, protects budget). Both are necessary, and they answer different questions.

Prove the limit works

Set a budget, then test it: send the pattern you expect an attacker to send and watch what the edge actually does. A surprising amount of rate limiting exists only in a slide deck, because nobody sent the hostile pattern through it.
Two checks are worth building into the deployment pipeline. First, a synthetic burst that must be rejected with the documented status and without leaking stack traces. Second, a synthetic legitimate burst that must not be rejected, because over-blocking is the failure mode nobody files a ticket for until the customer leaves.

Where enforcement lives

The edge is the right place for coarse limits; it is the wrong place for identity-aware limits. In practice:

  • Edge / gateway: coarse, per-IP or per-key, protects the tiers below against flood.
  • Service: per-subject and per-resource, knows what an operation costs.
  • Data layer: the last guard, protects the expensive query itself. Splitting enforcement this way keeps a single compromised credential from exhausting a shared backend while still stopping a volumetric flood before it reaches application code.

The uncomfortable part

Rate limiting hides operational debt. If a client legitimately needs 50,000 calls an hour, the honest fix is often a batch endpoint rather than a bigger limit. Raising the number is easier than redesigning the interface, so that is what usually happens, and the real problem stays in place.
Treat the limit as a measurement rather than a wall. When a limit fires on a legitimate client, the interesting question is why the client needs that volume, and the answer is frequently a design decision made years earlier that nobody has revisited.
Rate limiting is a control. It is not a substitute for an authorization model that holds under load, or for an interface that does not force the client to poll.

Top comments (0)