Dictionary Attack in Fastapi with Api Keys
Dictionary Attack in Fastapi with Api Keys — how this specific combination creates or exposes the vulnerability
A dictionary attack against an Fastapi API that relies solely on API keys exploits two conditions: predictable or leaked keys and the absence of request-rate controls. In Fastapi, developers often protect endpoints with an API‑key header (e.g., X‑API‑Key) and assume that secrecy alone is sufficient. However, if keys are generated with low entropy, stored insecurely, or leaked in logs, client-side automation can systematically iterate over a list of candidate keys to discover valid ones. Because Fastapi routes typically validate the key per request without additional safeguards, each attempt appears as a legitimate request, and failures are often indistinguishable from successes to the attacker.
When an API key is accepted without throttling or account lockout, a dictionary attack becomes practical. The attacker crafts a script that iterates over a wordlist of candidate keys, sending requests at a rate that stays below naive rate limits or by distributing requests across IPs. If the API responds with distinct status codes or timing differences for valid versus invalid keys, the attacker can learn which keys are valid. This is especially risky when the API exposes verbose error messages (e.g., 401 vs 403) or when authentication logic resides in middleware that does not uniformly enforce checks across all routes.
Moreover, dictionary attacks intersect with other security checks middleBrick performs. For example, weak keys may be exposed through insecure transmission (lack of encryption), and the presence of API keys does not prevent other vulnerabilities such as BOLA/IDOR if object-level authorization is missing. Since middleBrick scans unauthenticated attack surfaces, it can detect whether authentication is overly permissive, whether error handling leaks information, and whether rate limiting is absent or inconsistent. Even without credentials, the scanner can identify endpoints that accept API keys but do not enforce anti-automation controls, providing a clear remediation path.
Api Keys-Specific Remediation in Fastapi — concrete code fixes
To mitigate dictionary attacks, Fastapi APIs using API keys must combine key hygiene with request-level controls. Below are concrete, production-ready patterns that address authentication, rate limiting, and error handling.
- Use strong, high-entropy keys and store them securely. Generate keys with sufficient randomness (e.g., 256-bit) and avoid embedding them in source code. Use environment variables and a secrets manager at runtime.
- Implement consistent authentication middleware that uniformly validates API keys and does not leak information through error messages.
- Enforce rate limiting at the endpoint or global level to slow down or block automated guessing.
- Standardize HTTP responses to avoid distinguishing valid keys via status code or timing differences.
Example Fastapi implementation with secure API key validation and rate limiting:
import os
import time
import hashlib
from fastapi import Fastapi, Request, HTTPException, Depends, status
from fastapi.responses import JSONResponse
app = Fastapi()
# Simulated secure key store: in production, load from a secrets manager
# and use constant-time comparison.
VALID_KEY_HASH = hashlib.sha256(os.getenv("API_KEY_SECRET", "change-me").encode()).hexdigest()
def verify_api_key(request: Request) -> bool:
provided = request.headers.get("X-API-Key", "")
# Use constant-time comparison to avoid timing leaks
return hashlib.sha256(provided.encode()).hexdigest() == VALID_KEY_HASH
@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
# Simple in-memory rate limiter for demonstration; use Redis in production
if not hasattr(rate_limit_middleware, "request_log"):
rate_limit_middleware.request_log = {}
client_id = request.headers.get("X-API-Key") or request.client.host
now = time.time()
window = 60 # seconds
limit = 30 # requests per window
entry = rate_limit_middleware.request_log.get(client_id, [])
recent = [t for t in entry if now - t < window]
if len(recent) >= limit:
return JSONResponse(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
content={
"detail": "Rate limit exceeded. Try again later."
}
)
recent.append(now)
rate_limit_middleware.request_log[client_id] = recent
response = await call_next(request)
return response
@app.get("/secure-data")
async def get_secure_data(request: Request):
if not verify_api_key(request):
# Return a generic 401 to avoid revealing key validity
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Unauthorized"
)
return {"data": "This is protected"}
If you use dependencies, you can encapsulate key validation cleanly:
from fastapi import Depends, Security, HTTPException, status
def api_key_dependency():
def dependency(request: Request):
if not verify_api_key(request):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Unauthorized"
)
return True
return Depends(dependency)
@app.get("/items")
async def list_items(secure: bool = Security(api_key_dependency())):
return {"items": ["A", "B", "C"]}
Complement these code changes with operational practices: rotate keys periodically, audit logs for repeated failures, and integrate middleBrick into your workflow. The CLI allows quick scans from terminal with middlebrick scan <url>, while the GitHub Action adds API security checks to your CI/CD pipeline, failing builds if risk scores drop below your chosen threshold. For continuous monitoring, the Pro plan supports configurable schedules and alerts, and the MCP Server lets you scan APIs directly from your AI coding assistant.