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 acceptnone. - Use a required extractor like
JwtAuthorExtension<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 ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |