MEDIUM rate limiting bypassfastapibasic auth

Rate Limiting Bypass in Fastapi with Basic Auth

Rate Limiting Bypass in Fastapi with Basic Auth — how this specific combination creates or exposes the vulnerability

In Fastapi, combining Basic Authentication with insufficient rate limiting can enable authenticated-style abuse even when credentials are not verified per request. Basic Auth sends credentials in an Authorization header (Base64-encoded, not encrypted). If a Fastapi app applies rate limits only at the endpoint or IP level without tying limits to the authenticated identity, an attacker can rotate credentials to bypass limits.

Consider a login endpoint that uses Basic Auth for simplicity but does not enforce per-username rate limits. Because each unique username:password pair produces a distinct Base64 token, rotating credentials changes the Authorization header value. A global per-IP or per-endpoint limit may still be enforced by middleware or a gateway, but if the limit is coarse-grained, an attacker can exhaust the allowance for one user by cycling through many valid or invalid Basic Auth combinations. This can lead to denial-of-service against specific accounts or allow brute-force attempts against credentials within a single IP’s quota.

Another common pattern is conditional rate limiting based on authentication status. For example, limits might be stricter for unauthenticated requests and looser for authenticated requests. If the application determines authentication after the rate limit check, or if it trusts a header like X-Forwarded-For without validating the Authorization header, an attacker can omit or spoof headers to appear unauthenticated and receive the higher limit. Even when authentication is verified, using path-based or IP-based limits without considering the subject of authentication means one compromised credential can be shared or replayed to affect multiple users or tenants.

In API designs that rely on scopes or roles encoded in the token or username, improper mapping between identity and quota enables privilege escalation or resource exhaustion. A rate limiter that does not incorporate the resolved user identity may fail to enforce tenant boundaries, allowing one tenant to consume resources intended for another. This is especially risky when usernames map to different permission sets, as the limiter might apply the wrong tier based on outdated or missing identity context.

middleBrick detects these patterns by analyzing the unauthenticated attack surface and cross-referencing OpenAPI specifications with runtime behavior. Among its 12 security checks, the Rate Limiting assessment examines whether limits are applied consistently, whether they incorporate authentication context, and whether they remain effective when credentials are rotated. Findings include severity-ranked guidance such as binding limits to authenticated identities, using stable identifiers (e.g., user ID or tenant ID), and avoiding scope or IP-based allowances that ignore subject ownership.

Basic Auth-Specific Remediation in Fastapi — concrete code fixes

Secure remediation in Fastapi involves tying rate limits to the authenticated identity and ensuring limits are applied after proper credential validation. Use a dependency that decodes and verifies Basic Auth, extracts a stable user identifier, and enforces limits keyed by that identifier. Avoid coarse-grained limits based solely on IP or endpoint path when user-level protections are required.

Example: a Fastapi app with Basic Auth and per-user rate limiting using a memory store (replace with Redis or another shared store in production).

from fastapi import Fastapi, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from slowapi import Limiter
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
import base64
import logging

app = Fastapi()
security = HTTPBasic()
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter

# In-memory store for demo; use a persistent shared store in production
user_limits = {}  # {username: remaining_requests}

def decode_basic_auth(auth_header: str) -> tuple[str, str]:
    """Decode Basic Auth header and return (username, password)."""
    if not auth_header.startswith("Basic "):
        raise ValueError("Invalid Authorization header")
    encoded = auth_header.split(" ")[1]
    decoded = base64.b64decode(encoded).decode("utf-8")
    username, password = decoded.split(":", 1)
    return username, password

async def get_current_user(credentials: HTTPBasicCredentials = Depends(security)):
    username, password = decode_basic_auth(f"Basic {credentials.credential}")
    # Replace with secure credential verification (e.g., hashed check against DB)
    if not valid_user(username, password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Basic"},
        )
    return username

def valid_user(username: str, password: str) -> bool:
    # Stub: verify credentials securely, e.g., against a hashed store
    return True  # demo only

@app.middleware("http")
async def attach_limiter_state(request, call_next):
    # Ensure limiter state is available for the request
    return await call_next(request)

@app.post("/login")
@limiter.limit("5/minute", key_func=lambda r: r.state.rate_limit_key or get_remote_address(r))
async def login(username: str, user: str = Depends(get_current_user)):
    # Example of setting a per-user limit key; customize key_func for your store
    if user not in user_limits:
        user_limits[user] = 10  # per-user quota
    user_limits[user] -= 1
    if user_limits[user] < 0:
        raise RateLimitExceeded()
    return {"user": user, "status": "ok"}

Key points in this approach:

  • Credentials are decoded and verified on each request; the identity used for limiting is the resolved username after validation.
  • The rate limiter uses a key derived from the authenticated identity (or a stable user ID) rather than only IP or path, preventing rotation across credentials from bypassing limits.
  • Shared storage (e.g., Redis) is recommended for distributed deployments to ensure limits are consistent across instances.
  • Responses for rate-limited requests should use appropriate HTTP status codes and include Retry-After headers where applicable.

Additionally, avoid mixing authenticated and unauthenticated rate limit tiers without clear boundaries tied to identity. If you must offer looser limits for authenticated requests, ensure the authentication check occurs before the limit decision and that the subject is reliably mapped. middleBrick’s Rate Limiting check flags cases where identity is not incorporated, providing remediation guidance aligned with frameworks such as OWASP API Top 10 and common compliance requirements.

Related CWEs: resourceConsumption

CWE IDNameSeverity
CWE-400Uncontrolled Resource Consumption HIGH
CWE-770Allocation of Resources Without Limits MEDIUM
CWE-799Improper Control of Interaction Frequency MEDIUM
CWE-835Infinite Loop HIGH
CWE-1050Excessive Platform Resource Consumption MEDIUM

Frequently Asked Questions

Can rotating Basic Auth credentials reliably bypass IP-based rate limits in Fastapi?
Yes, if rate limits are applied per IP or endpoint without incorporating authenticated identity, rotating credentials (each producing a different Authorization header) can bypass coarse-grained limits, enabling account-specific abuse or brute-force attempts.
What is a secure pattern for Basic Auth rate limiting in Fastapi?
Verify credentials on each request, extract a stable user identifier, and apply per-user rate limits keyed by that identifier using a shared store in production. Ensure limits are enforced after authentication and not solely by IP or path.