HIGH rate limiting bypassflaskbearer tokens

Rate Limiting Bypass in Flask with Bearer Tokens

Rate Limiting Bypass in Flask with Bearer Tokens — how this specific combination creates or exposes the vulnerability

In Flask APIs that use Bearer token authentication, rate limiting can be bypassed when token validation is performed after the rate limiter is applied, or when the rate limiter key does not incorporate the authenticated identity. Consider a typical setup where a before_request handler validates the Bearer token and attaches the user identity to g, but the rate limit is applied based only on the request IP address or an API key header.

An authenticated attacker with a valid Bearer token can exploit this by cycling through stolen or compromised tokens. If the rate limiter does not include the user identity (e.g., the token’s subject or a user ID parsed from the token), each token appears as a new identity to the limiter, effectively allowing the attacker to bypass per-user limits. For example, if the limiter is configured as @limiter.limit("100/minute") and keyed by request.remote_addr or a non-identity header, submitting the same request with different valid tokens may avoid throttling because the limiter does not correlate requests to a single principal.

Another bypass pattern occurs when token validation is skipped for certain routes or when the authentication handler fails to reject requests with malformed tokens but still allows the request to proceed. If the rate limiter runs before the authentication check, unauthenticated probes can consume limits, but authenticated requests with valid tokens may escape identity-based tracking if the implementation does not enforce consistent user binding across all endpoints.

Real-world attack patterns mirror OWASP API Top 10 #5 (Broken Function Level Authorization) and can be chained with other weaknesses such as IDOR. For instance, an attacker could combine token cycling with BOLA to access other users’ resources without triggering rate-based alarms. The risk is elevated when the API also exposes endpoints that return sensitive data or perform privileged actions, because the lack of identity-aware throttling allows abusive enumeration or brute-force attempts to proceed undetected.

To detect this during a black-box scan, middleBrick runs checks that correlate authentication outcomes with rate limiting behavior across multiple requests using different Bearer tokens. The scanner validates whether the rate limiter incorporates a stable user or token identifier and whether token validation consistently precedes limit enforcement. Findings highlight cases where per-user limits are ineffective due to missing identity binding, providing remediation guidance to align token handling with rate limiting logic.

Bearer Tokens-Specific Remediation in Flask — concrete code fixes

Remediation focuses on ensuring the rate limiter key includes a stable identity derived from the Bearer token and that token validation is completed before any rate-limiting logic. Below is a secure Flask example using Flask-Limiter and a JWT-based Bearer token validation routine.

from flask import Flask, request, g
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import jwt

app = Flask(__name__)
limiter = Limiter(app=app, key_func=lambda: g.user_id if hasattr(g, 'user_id') else get_remote_address())

def get_user_id_from_token(token: str) -> str:
    # Replace with your secret/algorithm and proper error handling
    payload = jwt.decode(token, "your-secret-key", algorithms=["HS256"])
    return payload.get("sub")

@app.before_request
def authenticate_and_set_user():
    auth = request.headers.get("Authorization")
    if not auth or not auth.startswith("Bearer "):
        return {"error": "Unauthorized"}, 401
    token = auth.split(" ", 1)[1]
    try:
        user_id = get_user_id_from_token(token)
        g.user_id = user_id
    except jwt.InvalidTokenError:
        return {"error"; "Invalid token"}, 401

@app.route("/api/data")
@limiter.limit("100/minute")
def get_data():
    return {"data": "secure"}

Key points in the example:

  • The limiter’s key_func falls back to g.user_id when available, ensuring that authenticated requests are limited per user rather than per IP.
  • Token validation occurs in before_request, so g.user_id is set before the limiter evaluates the request. If authentication fails, the request is rejected before rate accounting is applied.
  • Using the token’s subject (sub) provides a stable identifier; avoid relying on ephemeral or non-unique claims.

For broader protection across many endpoints, apply the limiter at a higher level (e.g., using a decorator on a blueprint) and ensure every route that requires identity-based limits references g.user_id. If using multiple token types (e.g., API keys alongside Bearer tokens), include the token identifier in the key function to prevent cross-token bypass.

middleBrick’s scans verify that the rate limiter incorporates authenticated identity and that token validation precedes limit enforcement. The tool reports when identity-aware throttling is missing and offers remediation steps to bind user context to rate limits, reducing the risk of abuse via token cycling or enumeration.

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

Why is using request.remote_addr insufficient for Bearer token rate limiting in Flask?
Using request.remote_addr ties limits to the client IP, not the authenticated identity. An attacker with valid Bearer tokens can rotate tokens to bypass per-user limits because the limiter does not correlate requests to a stable user or token identifier, allowing abuse across multiple tokens from the same IP.
How can I ensure token validation always runs before rate limiting in Flask?
Place token validation in a before_request handler that sets a user identity on g, and configure the limiter’s key_func to use that identity. This guarantees that authenticated requests are limited per user and rejected unauthenticated requests before they consume rate limits.