HIGH session fixationaxumjwt tokens

Session Fixation in Axum with Jwt Tokens

Session Fixation in Axum with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Session fixation in Axum when JWT tokens are used occurs when an application accepts an attacker-provided token value or allows a predictable token to be set before authentication. With JWT-based sessions, the token itself often functions as the session identifier. If Axum endpoints accept a JWT from query parameters, headers, or cookies without validating provenance, an attacker can fixate a token on a victim’s browser and later use it after the victim authenticates.

Consider a login flow where Axum issues a JWT but does not rotate or explicitly bind the token to a freshly authenticated principal. If the application previously allowed unauthenticated requests to include a token query parameter that is later accepted post-login, an attacker can craft a link like https://api.example.com/login?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.... After the victim logs in, the same token is used to access protected routes, and because the server trusts the token without additional binding (e.g., to a nonce or session metadata), the authentication is effectively hijacked.

JWTs in Axum are often validated using middleware that checks signatures and claims. If the validation logic does not enforce strict issuer, audience, and jti (JWT ID) checks, or if tokens are accepted without verifying a one-time use or per-session binding, the fixed token remains valid across authentication boundaries. This is particularly risky when tokens contain embedded user identifiers and are stored in cookies with lax path/domain settings, enabling cross-origin leakage via referrers or insecure JavaScript access.

Common misconfigurations include:

  • Accepting JWTs from URL query strings, which can be leaked in logs, Referer headers, or browser history.
  • Not setting the HttpOnly and Secure flags on cookies storing tokens, enabling XSS-assisted fixation.
  • Failing to associate JWTs with a server-side session context or a per-authentication nonce, making token replay feasible after login.

Real-world analogs align with OWASP API Top 10 2023 A07:2023 — Identification and Authentication Failures, where broken session management enables takeover. Unlike traditional cookie-based sessions, JWTs require explicit rotation and binding to mitigate fixation; otherwise, the token value remains static across the authentication transition.

Jwt Tokens-Specific Remediation in Axum — concrete code fixes

Remediation focuses on ensuring JWTs are not predictable, not leakable via URLs, and strictly bound to authenticated sessions. In Axum, implement the following patterns using the jsonwebtoken crate and secure cookie handling.

1. Do not accept JWTs from query parameters

Reject or ignore JWTs passed via URLs. Configure your extractor to only read tokens from Authorization: Bearer headers or secure, HttpOnly cookies.

use axum::headers::authorization::{Authorization, Bearer};
use axum::extract::Request;
use axum::async_trait;

// Custom extractor that only allows Authorization header
pub struct JwtToken(String);

#[axum::async_trait]
impl FromRequest<S> for JwtToken 
where
    S: Send + Sync,
{
    type Rejection = (axum::http::StatusCode, String);

    async fn from_request(req: Request, _state: &S) -> Result {
        let auth = req.headers().get(axum::http::header::AUTHORIZATION)
            .ok_or((axum::http::StatusCode::UNAUTHORIZED, "Missing Authorization header".to_string()))?;
        let bearer = auth.parse::<Bearer>().map_err(|_| (axum::http::StatusCode::UNAUTHORIZED, "Invalid Authorization format".to_string()))?;
        Ok(JwtToken(bearer.token().to_string()))
    }
}

2. Rotate tokens upon login and bind to user context

Issue a new JWT after successful authentication and avoid reusing pre-authentication tokens. Include a jti (JWT ID) claim and optionally bind to the user’s session fingerprint.

use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,
    jti: String, // unique per authentication event
    iss: String,
    aud: String,
    exp: usize,
    iat: usize,
    // optional: bind to a device fingerprint or nonce
    nonce: String,
}

fn issue_token(user_id: &str, nonce: &str) -> String {
    let claims = Claims {
        sub: user_id.to_string(),
        jti: uuid::Uuid::new_v4().to_string(),
        iss: "api.example.com".to_string(),
        aud: "api.example.com".to_string(),
        exp: (chrono::Utc::now() + chrono::Duration::hours(1)).timestamp() as usize,
        iat: chrono::Utc::now().timestamp() as usize,
        nonce: nonce.to_string(),
    };
    encode(
        &Header::new(Algorithm::HS256),
        &claims,
        &EncodingKey::from_secret("super-secret-key-change-in-prod".as_ref()),
    ).expect("Failed to encode token")
}

3. Store tokens securely in cookies with strict attributes

If using cookies to store JWTs, set HttpOnly, Secure, SameSite=Strict, and limit the path.

use axum::response::Response;
use axum::http::{Cookie, CookieJar};

pub fn set_secure_token_cookie(mut response: Response, token: &str) -> Response {
    let cookie = Cookie::build(("token", token))
        .http_only(true)
        .secure(true)
        .same_site(axum::http::cookie::SameSite::Strict)
        .path("/")
        .max_age(time::Duration::hours(1).to_std().unwrap())
        .finish();
    response.headers_mut().insert(
        axum::http::header::SET_COOKIE,
        cookie.to_string().parse().unwrap(),
    );
    response
}

4. Validate claims rigorously and enforce short lifetimes

Use strong validation including issuer, audience, and jti checks. Short-lived tokens reduce the window for fixation and replay.

use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};

fn validate_token(token: &str) -> Result<jsonwebtoken::TokenData<Claims>, jsonwebtoken::errors::Error> {
    let validation = Validation::new(Algorithm::HS256);
    let mut validation = validation;
    validation.validate_exp = true;
    validation.validate_iss = true;
    validation.validate_aud = true;
    validation.required_spec_claims = vec!["iss".into(), "aud".into(), "exp".into(), "jti".into()];
    decode::<Claims>(
        token,
        &DecodingKey::from_secret("super-secret-key-change-in-prod".as_ref()),
        &validation,
    )
}

Frequently Asked Questions

Can middleBrick detect session fixation vulnerabilities in Axum JWT flows?
middleBrick scans unauthenticated attack surfaces and can identify indicators such as JWT acceptance from query parameters or missing token binding. Findings include severity, descriptions, and remediation guidance aligned with OWASP API Top 10.
How does middleBucket handle JWT security checks compared to other scanners?
middleBrick includes LLM/AI Security checks that specifically probe for system prompt leakage, prompt injection, and output risks; it also supports OpenAPI/Swagger spec analysis with full $ref resolution to cross-reference runtime behavior against spec definitions for endpoints using JWTs.