Cors Wildcard in Axum with Jwt Tokens
Cors Wildcard in Axum with Jwt Tokens — how this specific combination creates or exposes the vulnerability
A CORS wildcard (Access-Control-Allow-Origin: *) combined with JWT-based authentication in an Axum service can unintentionally expose protected endpoints to any origin. This occurs when the server includes both a wildcard CORS policy and JWT validation in the same response for authenticated requests. Because the browser enforces CORS, a response that includes an Authorization header and Access-Control-Allow-Origin: * can be read by any webpage, even if the backend correctly validates the JWT. This contradicts the intent of scope-based access control, effectively making authenticated routes accessible cross-origin without origin restrictions. The risk is especially pronounced when routes accept credentials (cookies or authorization headers) while also returning a wildcard, because it allows an attacker-controlled site to make authenticated requests on behalf of a victim and potentially read the responses. This pattern can lead to privilege escalation if the endpoint exposes sensitive data or performs actions on behalf of the authenticated user. In the context of the 12 security checks, this is flagged as a CORS misconfiguration that may map to authentication bypass vectors and should be treated as a finding with severity high. Note that CORS is a browser-enforced mechanism; non-browser clients are not restricted, but the exposure still increases the attack surface for web-based attackers.
Jwt Tokens-Specific Remediation in Axum — concrete code fixes
To remediate CORS over-permissiveness while keeping JWT validation intact in Axum, restrict Access-Control-Allow-Origin to known origins and handle credentials selectively. Below are two concrete Axum examples: one insecure wildcard setup and one corrected, origin-restricted setup.
Insecure example (to avoid)
use axum::{
routing::get,
Router,
http::header::{ACCESS_CONTROL_ALLOW_ORIGIN, AUTHORIZATION},
response::Response,
};
use std::net::SocketAddr;
async fn handler_with_wildcard() -> Response {
let cors_middleware = tower_cors::CorsLayer::new()
.allow_origin(tower_cors::AllowOrigin::wildcard())
.allow_headers(vec!["authorization"].into_iter().map(Into::into).collect());
// This response exposes authenticated routes to any origin
Router::new()
.route("/me", get(|_: String| async { format!("Hello") }))
.layer(cors_middleware)
.into_response()
}
#[tokio::main]
async fn main() {
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(handler_with_wildcard().into_make_service())
.await
.unwrap();
}
Corrected example with origin restriction and JWT validation
use axum::{
routing::get,
Router,
http::header::{ACCESS_CONTROL_ALLOW_ORIGIN, AUTHORIZATION},
response::Response,
extract::State,
};
use std::net::SocketAddr;
use tower_cors::CorsLayer;
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation, TokenData, Header};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
exp: usize,
}
async fn handler_protected(
State(cors_config): State,
auth_header: Option,
) -> String {
// Validate JWT from header
let token = match auth_header.and_then(|v| v.to_str().ok()) {
Some(t) => t,
None => return "Unauthorized".into(),
};
let validation = Validation::new(Algorithm::HS256);
let token_data = decode::(
token,
&DecodingKey::from_secret("secret".as_ref()),
&validation,
).map_err(|_| "Invalid token")?;
format!("User: {}", token_data.claims.sub)
}
async fn build_router() -> Router {
let allowed_origin = "https://trusted.example.com".parse().unwrap();
let cors = CorsLayer::new()
.allow_origin(allowed_origin)
.allow_headers(vec!["authorization"].into_iter().map(Into::into).collect())
.allow_methods(vec!["GET", "POST"].into_iter().map(Into::into).collect());
Router::new()
.route("/me", get(handler_protected))
.layer(cors)
}
#[tokio::main]
async fn main() {
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(build_router().into_make_service())
.await
.unwrap();
}
In the corrected setup, Access-Control-Allow-Origin is set to a specific trusted origin rather than a wildcard, and the JWT validation logic runs before the response is constructed. This ensures that even if a browser sends an Authorization header, the response is not readable by unauthorized origins. For continuous protection, use the Pro plan to enable continuous monitoring and fail builds automatically if risk scores exceed your threshold via the GitHub Action. The MCP Server can also be used to scan APIs directly from your AI coding assistant to catch such misconfigurations during development.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |