HIGH api key exposureaxumjwt tokens

Api Key Exposure in Axum with Jwt Tokens

Api Key Exposure in Axum with Jwt Tokens — how this specific combination creates or exposes the vulnerability

In Axum applications that use JWTs for authorization, developers sometimes conflate signing tokens with protecting every secret. When an API key is embedded in a JWT payload (for example as a custom claim like api_key), the token is usually transmitted in the Authorization: Bearer <token> header. If the API key is also used as a signing secret or is derivable from the JWT structure, an exposure in one layer can weaken the other. This combination can expose the API key when JWT handling is misconfigured or when tokens are logged, leaked in URLs, or stored insecurely on the client.

A common pattern that creates risk is using a static or low-entropy API key as the JWT signing key. If the signing key is compromised, an attacker can forge tokens and potentially reuse or rotate associated API keys. Additionally, if the JWT payload includes the raw API key and the token is stored in browser local storage or logs, accidental data exposure can occur through client-side debugging or log aggregation systems. Middleware that parses JWTs may also inadvertently propagate the key in structured logs, especially if the token is large and contains sensitive claims.

Another vector involves routing and reverse proxy setups where the Authorization header is forwarded to downstream services. If those services log request headers verbatim, the JWT (and thus the embedded or related API key) can end up in centralized logging. SSRF issues can also lead to internal metadata endpoints being called with the token, further expanding the exposure surface. Because JWTs are often long-lived compared to ephemeral request keys, a leaked JWT can lead to extended access if the API key it references is not rotated frequently.

Even when JWTs are properly validated, implementation mismatches between token audiences and API scopes can cause privilege confusion. For example, an endpoint expecting a service-specific API key might incorrectly trust a JWT intended for a different client if audience (aud) validation is skipped. This misalignment can allow an attacker presenting a valid JWT to act as if they possess a higher-privilege API key. MiddleBrick’s checks for Authentication and BOLA/IDOR help surface these logic gaps by testing unauthenticated endpoints and verifying ownership boundaries across subjects and objects.

Because middleBrick tests unauthenticated attack surfaces and includes checks for Data Exposure and Unsafe Consumption, it can detect scenarios where JWTs or API keys appear in responses, logs, or error messages. The scanner does not modify or block traffic; it reports findings with remediation guidance, helping teams understand how a leaked JWT might correlate with an exposed API key in their Axum service.

Jwt Tokens-Specific Remediation in Axum — concrete code fixes

Secure handling of JWTs in Axum requires strict separation of concerns: use JWTs for identity and authorization assertions, and treat API keys as separate credentials managed with distinct lifetimes and storage rules. Below are concrete, idiomatic examples that reduce the risk of combined exposure.

1. Use JWTs for identity, keep API keys in secure server-side storage

Do not embed raw API keys in JWT claims. Instead, store API keys in a server-side vault or environment configuration, and reference them by a non-sensitive token identifier inside the JWT.

// Cargo.toml dependencies
// axum = "0.6"
// jsonwebtoken = "0.9"
// serde = { version = "1.0", features = ["derive"] }
// tower-http = { version = "0.4", features = ["set-header"] }

use axum::{
    async_trait, body::Body, extract::Request, http::Response, middleware::Next,
};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::env;

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,        // user or service ID
    scope: String,      // e.g., "read:metrics"
    exp: usize,         // expiration
    jti: String,        // JWT ID for revocation tracking
    // NOT: api_key field
}

async fn validate_jwt(token: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
    let secret = env::var("JWT_SIGNING_SECRET").expect("JWT_SIGNING_SECRET must be set");
    let validation = Validation::new(Algorithm::HS256);
    let token_data = decode:: String {
    // Fetch from secure store, e.g., AWS Secrets Manager, Vault, or env per service
    env::var(format!("API_KEY_{}", subject)).unwrap_or_else(|_| "fallback-placeholder".into())
}

// Example middleware or handler usage:
async fn handler(
    Request {
        headers,
        extension,
        ..
    }: Request,
) -> Response<Body> {
    let auth = headers.get("authorization")
        .and_then(|v| v.to_str().ok())
        .and_then(|s| s.strip_prefix("Bearer "))
        .unwrap_or("");

    let claims = match validate_jwt(auth).await {
        Ok(c) => c,
        Err(_) => return Response::builder().status(401).body(Body::from("invalid token")).unwrap(),
    };

    let api_key = get_api_key_for_subject(&claims.sub);
    // Use api_key for downstream service authentication, not from the JWT
    Response::new(Body::from(format!("scoped key: {}, subject: {}", api_key, claims.sub)))
}

2. Enforce audience and scope validation

Always validate the aud claim and limit scope within the JWT validation step to prevent privilege confusion.

let mut validation = Validation::new(Algorithm::HS256);
validation.validate_aud = true;
validation.set_audience(&["my-api-service"]);
let token_data = decode::<Claims>(token, &DecodingKey::from_secret(secret.as_ref()), &validation)?;

3. Rotate keys and avoid static signing secrets

Use environment rotation or key provider integration to change JWT_SIGNING_SECRET and API_KEY_* values without redeploying code. Shorten JWT expirations and implement token revocation via jti checks where feasible.

4. Prevent logging and leakage

Ensure JWTs are not written to access logs or error traces. In Axum, customize logging layers to strip sensitive headers before output.

use tower_http::trace::TraceLayer;
use tracing_subscriber::filter::LevelFilter;

let app = Router::new()
    .route("/secure", handler)
    .layer(
        TraceLayer::new_for_http()
            .make_span_with(|_| {
                // custom span that redacts authorization header
                tracing::info_span::default()
            })
    );

These steps reduce the chance that a JWT or an API key alone can be exploited. Combined with middleBrick’s checks for Authentication, Data Exposure, and Unsafe Consumption, teams can detect residual leakage and refine their Axum service configuration.

Frequently Asked Questions

Can middleBrick remove or rotate exposed API keys in Axum services?
middleBrick detects and reports API key exposure in JWT-related flows, but it does not modify, rotate, or remove keys. You must rotate keys and adjust Axum configuration using your own secure processes.
Does scanning with the middleBrick CLI or GitHub Action require API keys or authentication on the Axum service?
No. middleBrick scans the unauthenticated attack surface by default and does not require credentials. Authentication can be added later if needed, but initial scans are black-box and rely on public endpoints.