HIGH rate limiting bypassaxumjwt tokens

Rate Limiting Bypass in Axum with Jwt Tokens

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

A Rate Limiting Bypass in Axum using JWT tokens typically occurs when rate limiting is applied only to unauthenticated paths or to a coarse identifier such as IP address, while token-based authentication is not consistently included in the limiting key. In this configuration, an authenticated user can make many requests within a short window because each request carries a different valid JWT containing the same user identity. The server may treat each token as a distinct context and fail to associate them with a shared rate limit bucket for the underlying user or client. This allows an attacker who compromises a single account or who can generate multiple tokens to exceed intended request caps without triggering defenses.

Additionally, if the rate limiting implementation inspects only path or IP and does not reliably extract and normalize the subject (sub) claim from the JWT, two different tokens representing the same user may be rate limited independently. Inconsistent token parsing, such as failing to handle token formats with the same payload but different encodings or issuers, can further weaken the limiting logic. Because the application trusts the token scope to enforce identity, missing canonicalization of claims leads to a split-bucket effect where the effective limit per user is multiplied by the number of distinct tokens they can obtain. This is especially relevant when token lifetimes are long or when refresh flows issue new tokens without revoking prior ones.

Real-world attack patterns mirror API abuse scenarios cataloged in the OWASP API Security Top 10, including excessive data consumption and broken object level authorization when rate limits are weak. A scanner that tests unauthenticated surfaces and then validates authenticated behavior using JWT tokens can surface these inconsistencies by submitting requests with varied tokens but identical user contexts. Findings often highlight missing normalization of the sub claim, lack of binding between token identity and rate limit buckets, and absence of coordinated limits across authentication and unauthenticated routes. Remediation focuses on designing a stable, token-aware rate limiting key, canonicalizing claims, and ensuring that limits apply to the underlying user or client rather than to ephemeral tokens alone.

Jwt Tokens-Specific Remediation in Axum — concrete code fixes

To mitigate Rate Limiting Bypass in Axum when JWT tokens are used, implement rate limiting on a stable user identifier extracted and canonicalized from the token claims. Prefer the sub claim for user identity, and ensure the same normalization logic is applied across all endpoints. Below are concrete, working Axum examples that demonstrate how to structure the middleware and route handling to bind rate limits to the user rather than to the token or IP.

Consistent JWT extraction and claim normalization

Define a helper that decodes the token, validates required fields, and returns a canonical user key. This avoids variations caused by issuer differences or encoding differences for the same subject.

use axum::{async_trait, extract::{FromRequest, RequestParts}};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};
use std::{convert::Infallible, sync::Arc};

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,
    exp: usize,
    iss: String,
}

struct UserId(String);

#[async_trait]
impl FromRequest<S> for UserId
where
    S: Send + Sync,
{
    type Rejection = Infallible;

    async fn from_request(req: &mut RequestParts<S>) -> Result<Self, Self::Rejection> {
        let auth = req.headers().get("authorization")
            .and_then(|v| v.to_str().ok())
            .and_then(|s| s.strip_prefix("Bearer "))
            .unwrap_or("");

        let token_data = decode::

Rate limiting keyed by user ID in Axum middleware

Implement a middleware that reads the user identifier and applies a per-user sliding window or token bucket. Use an in-memory store for examples; in production you would use Redis or another shared store to coordinate limits across instances.

use axum::{middleware::Next, response::Response, Extension, Json};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

#[derive(Clone)]
struct RateLimiter {
    limits: Arc<Mutex<HashMap<String, (usize, std::time::Instant)>>>,
    max_requests: usize,
    window: std::time::Duration,
}

impl RateLimiter {
    fn new(max_requests: usize, window: std::time::Duration) -> Self {
        RateLimiter {
            limits: Arc::new(Mutex::new(HashMap::new())),
            max_requests,
            window,
        }
    }

    fn allow(&self, user_id: &str) -> bool {
        let mut map = self.limits.lock().unwrap();
        let entry = map.entry(user_id.to_string()).or_insert((0, self.window.instant()));
        if entry.1.elapsed() > self.window {
            entry.0 = 1;
            entry.1 = self.window.instant();
            true
        } else if entry.0 < self.max_requests {
            entry.0 += 1;
            true
        } else {
            false
        }
    }
}

async fn rate_limit_middleware(
    UserId(user_id): UserId,
    limiter: Extension<RateLimiter>,
    next: Next,
) -> Response {
    if limiter.allow(&user_id) {
        next.run().await
    } else {
        Response::builder()
            .status(429)
            .body("Too Many Requests".into())
            .unwrap()
    }
}

Applying the middleware and routes

Wire the shared limiter into your Axum app and protect routes that should be bounded per user. This ensures that even if an attacker obtains multiple JWTs for the same user, the combined request volume is constrained by a single bucket.

use axum::{routing::get, Router};

let limiter = RateLimiter::new(100, std::time::Duration::from_secs(60));
let app = Router::new()
    .route("/profile", get(|| async { "ok" }))
    .layer(Extension(limiter))
    .layer(axum::middleware::from_fn_with_state(
        (),
        rate_limit_middleware,
    ));

By tying the rate limit key to the canonical user ID extracted from the JWT, you eliminate token-driven splitting of limits and reduce the surface for Rate Limiting Bypass in Axum with JWT tokens. Combine this with short token lifetimes and revocation awareness to further limit abuse windows.

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 does using multiple JWT tokens for the same user bypass rate limits?
Because rate limiting may be scoped to token or IP rather than to a canonical user identifier such as the sub claim. If each token is treated as a separate context, an attacker can rotate tokens to multiply the effective limit.
What claim should be used for canonical user identification in Axum JWT rate limiting?
Use the sub claim as the canonical subject identifier after validating issuer and expiration. Normalize the value and bind rate limit buckets to this user-level key rather than to the token string or IP address.