Rate Limiting Bypass in Fastapi with Api Keys
Rate Limiting Bypass in Fastapi with Api Keys — how this specific combination creates or exposes the vulnerability
In FastAPI applications that rely on API keys for access control, rate limiting can be bypassed when key validation and request counting are not tightly coupled. API keys are often passed via headers, query parameters, or cookies, and if the rate limiter does not enforce limits per-key—or if limits are applied before key validation—an attacker can reuse or rotate keys to exhaust quotas without being throttled.
This becomes a BFLA/Privilege Escalation concern when higher-privilege keys (e.g., admin keys) are subject to the same limits as lower-privilege keys, or when limits are enforced at a global level rather than per-key. For example, if a FastAPI endpoint validates the API key after checking a global rate limit, an unauthenticated attacker could make many requests with different keys or tokens until the key validation step is reached, effectively draining the rate limit pool.
Another common pattern is storing rate-limiting state in memory or in a shared cache without associating it with the key. If the cache key does not include the API key value—or if the key is normalized or truncated—different keys may map to the same cache entry, allowing one key’s usage to count against another. This misalignment enables attackers to cycle through valid keys while staying under the global threshold.
Additionally, if the API key is accepted both as a header and as a query parameter, and only one channel is rate-limited, an attacker can switch inputs to bypass enforcement. MiddleBrick’s 12 security checks include BFLA/Privilege Escalation and Rate Limiting runs in parallel, detecting these key-to-limit misalignments by correlating authentication findings with request-volume anomalies across unauthenticated endpoints.
Because middleBrick scans the unauthenticated attack surface in 5–15 seconds, it can surface cases where rate limiting does not vary by API key, where keys are not included in cache identifiers, or where limits are applied before authentication. The resulting findings include severity ratings and remediation guidance mapped to frameworks such as OWASP API Top 10 and PCI-DSS, helping teams understand the risk without requiring internal instrumentation.
Api Keys-Specific Remediation in Fastapi — concrete code fixes
To prevent rate-limiting bypass related to API keys in FastAPI, enforce limits per-key and ensure key validation occurs before any rate-limiting logic. Below are concrete, working examples that demonstrate a secure pattern using a dependency that validates the key and applies rate limits in the same step.
Example 1: In-memory rate limiting per API key with fastapi and slowapi
from fastapi import FastAPI, Depends, HTTPException, Header
from slowapi import Limiter
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
app = FastAPI()
limiter = Limiter(key_func=get_remote_address) # fallback, not key-based
# In production, replace with a per-key limiter such as redis-backed slowapi or a custom dependency
@app.get("/secure-data")
async def get_secure_data(
api_key: str = Header(None, description="API key required"),
limiter: Limiter = Depends(lambda r: limiter) # simplified for example
):
if not api_key or not validate_key(api_key):
raise HTTPException(status_code=401, detail="Invalid API key")
# key validated before rate limit enforcement handled by slowapi via request context
return {"data": "sensitive but authorized"}
def validate_key(api_key: str) -> bool:
# Replace with constant-time lookup against a secure store
allowed = {"test-key-123": "admin", "read-only-456": "user"}
return api_key in allowed
Example 2: Custom dependency that enforces per-key rate limits using Redis
from fastapi import Depends, HTTPException, Header, FastAPI
import aioredis
import time
app = FastAPI()
redis = aioredis.from_url("redis://localhost")
async def rate_limit_per_key(
api_key: str = Header(None, description="API key required"),
max_requests: int = 10,
window: int = 60
):
if not api_key or not validate_key(api_key):
raise HTTPException(status_code=401, detail="Invalid API key")
key = f"rate_limit:{api_key}"
current = await redis.get(key)
if current is None:
await redis.set(key, 1, ex=window)
else:
if int(current) >= max_requests:
raise HTTPException(status_code=429, detail="Rate limit exceeded")
await redis.incr(key)
def validate_key(api_key: str) -> bool:
allowed = {"test-key-123": "admin", "read-only-456": "user"}
return api_key in allowed
@app.get("/data")
async def get_data(
_ = Depends(rate_limit_per_key) # enforces per-key limit
):
return {"result": "ok"}
Key remediation practices
- Always validate the API key before incrementing or checking any rate-limiting counter.
- Include the exact API key value in the rate-limiting cache key (e.g.,
rate_limit:{api_key}) to prevent cross-key exhaustion. - Use constant-time comparison for key validation to mitigate timing attacks.
- Avoid mixing authentication and rate-limiting checks across separate middleware layers unless order and context are explicitly verified.
- Prefer a distributed store like Redis for shared state in multi-instance deployments to ensure counts are consistent across workers.
These patterns align with findings middleBrick reports, which highlight misalignment between authentication and rate limiting. The CLI (middlebrick scan <url>) and GitHub Action can be used to detect such issues in CI/CD, while the Web Dashboard tracks scores and findings over time. For teams on the Pro plan, continuous monitoring and configurable scan schedules help catch regressions before they reach production.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |