| name | apply-api-rate-limiting |
| description | Use when building any API endpoint — especially authentication endpoints, resource-creation endpoints, and any operation with a per-user or per-tenant cost — to protect against abuse, brute force, and resource exhaustion. |
| source | OWASP API Security Top 10 2023 API4 (Unrestricted Resource Consumption); OWASP API Security Cheat Sheet (owasp.org/www-project-cheat-sheets); CWE-770 |
| tags | ["security","owasp","rate-limiting","throttling","api","dos-prevention","developer"] |
Apply API Rate Limiting
Enforce per-client request quotas using token bucket or sliding window algorithms — protecting against brute force, scraping, and resource exhaustion attacks.
Why This Is Best Practice
Adopted by: OWASP API Security Top 10 2023 API4 (Unrestricted Resource Consumption) is a top API vulnerability. AWS API Gateway, Google Cloud Endpoints, Azure APIM, Kong, Nginx, and Cloudflare all provide built-in rate limiting. Stripe, GitHub, and Twitter/X enforce rate limits on every API endpoint with well-documented headers. PCI DSS v4.0 Requirement 8.3.4 mandates account lockout after failed authentication attempts — which requires rate limiting.
Impact: Rate limiting prevents brute force credential attacks (see also design-session-management), credential stuffing (using credential lists), API scraping (competitive intelligence theft), and denial of service via resource exhaustion. Without authentication endpoint rate limits, attackers test millions of passwords in hours. Without resource endpoint limits, a single user can consume 100% of server capacity.
Why best: Client-side debouncing and application-level retries are the alternatives — they don't prevent malicious clients from ignoring them. Server-side rate limiting enforced at the network edge (or application layer with a distributed counter) is the only enforceable mechanism.
Sources: OWASP API Security Top 10 2023 API4; OWASP API Security Cheat Sheet; CWE-770; Stripe rate limiting design
Steps
-
Implement token bucket rate limiting — smooth limit that allows short bursts:
import time
import redis
class TokenBucket:
def __init__(self, redis_client, key, rate, capacity):
self.redis = redis_client
self.key = key
self.rate = rate
self.capacity = capacity
def consume(self, tokens=1):
now = time.time()
pipe = self.redis.pipeline()
pipe.hgetall(self.key)
results = pipe.execute()
data = results[0]
last_time = float(data.get(b'last_time', now))
stored = float(data.get(b'tokens', self.capacity))
elapsed = now - last_time
stored = min(self.capacity, stored + elapsed * self.rate)
if stored < tokens:
return False
stored -= tokens
pipe = self.redis.pipeline()
pipe.hset(self.key, mapping={'tokens': stored, 'last_time': now})
pipe.expire(self.key, int(self.capacity / .rate) + )
pipe.execute()
Rules
- Always rate limit authentication endpoints regardless of other protections — credential stuffing is fully automated.
- Rate limit by user ID (not just IP) for authenticated endpoints — a compromised account can still abuse from many IPs.
- Return 429 (Too Many Requests) with
Retry-After — not 403 (which suggests permanent denial).
- Distributed systems need distributed rate limit counters (Redis, Memcached) — in-memory counters per instance are bypassable by distributing requests across instances.
Common Mistakes
- Only rate limiting unauthenticated endpoints — authenticated users can also abuse resource-intensive endpoints.
- Using fixed windows (reset every minute) — vulnerable to bursting at the window boundary; use sliding window or token bucket.
- Ignoring
X-Forwarded-For header behind a load balancer — all clients appear to come from the load balancer's IP; use the rightmost untrusted IP in the header chain.
- Rate limiting login attempts but not password reset — password reset is a brute force vector for account enumeration.