HIGH auth bypassaxumjwt tokens

Auth Bypass in Axum with Jwt Tokens

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

When using JWT tokens in an Axum application, auth bypass risks often arise from missing or inconsistent authorization checks between the HTTP handler layer and the JWT validation layer. Axum does not enforce authentication or authorization by default; it is the developer’s responsibility to integrate a JWT validation layer and ensure every protected route consults it. A common pattern is to call a middleware or extractor that validates the token and attaches user claims to the request extensions, but if some routes omit this extractor or rely only on optional extraction, an attacker can reach endpoints without a valid token or with an altered token.

Consider an Axum router built like this:

use axum::{routing::get, Router};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};

async fn public_endpoint() -> &'static str { "public" }

async fn protected_endpoint() -> &'static str { "protected" }

fn make_router() -> Router {
    Router::new()
        .route("/public", get(public_endpoint))
        .route("/protected", get(protected_endpoint)) // missing auth extractor
}

In the example above, /protected does not require a JWT token, enabling an auth bypass regardless of how robust the token validation is elsewhere. Even when a route uses an extractor, bypass can occur if the validation is too permissive, such as not enforcing the alg field or accepting unsigned tokens (Algorithm::none) during decoding. The JSON Web Token specification allows the algorithm to be indicated by the token header; if the server does not explicitly set a required algorithm, an attacker could supply a token with { "alg": "none" } and a valid payload, bypassing signature verification entirely.

Another subtle bypass arises when token validation succeeds but the authorization logic (scopes or roles) is not enforced at the handler or via a proper authorization extractor. For example, a JWT may contain a scope claim, but if the handler does not check that scope, a user with a minimally privileged token can access administrative functions. Additionally, if the application reuses a token verification function but accidentally skips verification for certain paths via route prefix grouping or conditional middleware, an attacker can exploit the uncovered path.

Real-world attack patterns mirror these mistakes. For instance, a misconfigured Axum service might validate tokens for routes under /api/v1/* but omit validation for legacy routes under /api/legacy/*. In another scenario, a developer might use optional extraction (Option<JsonWebTokenClaims>) instead of required extraction, causing the handler to proceed with a None when the token is absent or invalid. These gaps allow unauthenticated access or privilege escalation, aligning with common API risks like BOLA/IDOR when object-level authorization is also missing.

Because middleBrick performs unauthenticated black-box scanning, it can detect endpoints that lack JWT requirements or have inconsistent validation rules by probing routes without tokens and inspecting responses for unprotected data exposure. Its checks include input validation and authentication testing, which help surface these bypass patterns without needing credentials or internal architecture details.

Jwt Tokens-Specific Remediation in Axum — concrete code fixes

To prevent auth bypass when using JWT tokens in Axum, enforce token validation uniformly across all protected routes and tighten the decoding configuration. Always specify the expected algorithm and reject unsigned tokens. Below is a secure pattern using an extractor that validates the token and attaches claims to the request extensions, ensuring every route can depend on authenticated identity.

use axum::{{
    async_trait,
    extract::{FromRequest, Request},
    response::Response,
    Extension, Json, Router,
}};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation, TokenData};
use serde::{Deserialize, Serialize};
use std::convert::Infallable;

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

struct JwtAuth {
    claims: Claims,
}

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

    async fn from_request(req: Request, _: &S) -> Result<Self, Self::Rejection> {
        let token = req
            .headers()
            .get("authorization")
            .and_then(|v| v.to_str().ok())
            .and_then(|s| s.strip_prefix("Bearer "))
            .ok_or_else(|| Response::builder().status(401).body("Missing or invalid Authorization header".into()))?;

        let validation = Validation::new(Algorithm::HS256);
        let token_data = decode::<Claims>(token, &DecodingKey::from_secret("secret".as_ref()), &validation)
            .map_err(|_| Response::builder().status(401).body("Invalid token".into()))?;

        Ok(JwtAuth { claims: token_data.claims })
    }
}

async fn public_endpoint() -> &'static str { "public" }

async fn protected_endpoint(Extension(claims): Extension<TokenData<Claims>>) -> String {
    format!("protected: user={}", claims.claims.sub)
}

fn make_secure_router() -> Router {
    Router::new()
        .route("/public", get(public_endpoint))
        .route("/protected", get(protected_endpoint))
        .layer(axum::middleware::from_fn(|req, next| async move {
            // Optional: centralize logging or rate limiting here
            next.run(req).await
        }))
}

Key remediation steps:

  • Always set Validation::new(Algorithm::HS256) (or the algorithm you use) and avoid default or permissive validation that might accept none.
  • Use a required extractor like JwtAuth or Extension<TokenData<Claims>> on every protected handler; never rely on optional extraction for security-critical routes.
  • Check scopes or roles inside handlers or via another extractor; JWT validation alone does not enforce authorization.
  • Ensure consistent route coverage: avoid separate route groups that skip the auth extractor, and keep middleware ordering predictable.

By combining uniform extraction, strict algorithm enforcement, and scope checks, you reduce the likelihood of auth bypass. middleBrick can validate these remediations by scanning the API without authentication and confirming that protected endpoints reject unauthenticated requests, while its OpenAPI/Swagger analysis cross-references spec definitions with runtime behavior to highlight inconsistencies.

Related CWEs: authentication

CWE IDNameSeverity
CWE-287Improper Authentication CRITICAL
CWE-306Missing Authentication for Critical Function CRITICAL
CWE-307Brute Force HIGH
CWE-308Single-Factor Authentication MEDIUM
CWE-309Use of Password System for Primary Authentication MEDIUM
CWE-347Improper Verification of Cryptographic Signature HIGH
CWE-384Session Fixation HIGH
CWE-521Weak Password Requirements MEDIUM
CWE-613Insufficient Session Expiration MEDIUM
CWE-640Weak Password Recovery HIGH

Frequently Asked Questions

How can I test whether my Axum endpoints are vulnerable to JWT auth bypass without credentials?
You can send requests to protected endpoints without an Authorization header or with a malformed token and inspect whether they return 200 OK instead of 401/403. Tools like middleBrick perform such unauthenticated probes and flag endpoints that do not enforce JWT validation.
Does using middleware in Axum guarantee protection against JWT auth bypass?
Middleware alone does not guarantee protection; the middleware must explicitly validate the JWT and the router must ensure every protected route uses the required authentication extractor. If some routes skip the middleware or use optional extraction, bypass remains possible.