Session Fixation in Actix with Jwt Tokens
Session Fixation in Actix with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Session fixation occurs when an application allows an attacker to force a user to use a known session identifier. In Actix applications that rely on JWTs for session-like behavior, the risk arises when the server accepts a client-supplied token without ensuring it was issued specifically for that user session. JWTs are often used in a stateless, bearer-token style; if the token value is exposed or predictable, or if the application does not bind the token to a per-user, per-authentication context, the token can be reused across sessions.
In Actix, developers sometimes issue a JWT at login but also accept an incoming JWT from query parameters, headers, or cookies without strict validation of the token’s origin. If an attacker sets a victim’s token (e.g., via URL manipulation or a crafted link) and the server trusts that token as proof of identity without confirming it was freshly issued after authentication, the victim’s identity can be hijacked. This is particularly relevant when JWTs lack proper 'jti' (JWT ID) claims, short lifetimes, or strict issuer/audience checks, and when the application does not rotate or bind tokens to a post-authentication context.
Consider an Actix handler that accepts an Authorization header but does not verify that the token was generated after the user authenticated. An attacker could craft a URL like https://api.example.com/action?token=known_value and trick a victim into visiting it. If the server trusts known_value as the user’s identity, the attacker inherits the victim’s permissions. The root cause is a lack of server-side binding between the authentication event and the presented JWT, combined with acceptance of tokens that were not issued as part of the current login flow.
Additionally, if Actix services exchange JWTs internally and do not validate scopes or intended recipients, a leaked or fixed token may permit access to multiple services, effectively creating a session-fixation vector across a distributed system. Without per-session nonce or token-binding mechanisms, an attacker can reuse a token across requests, bypassing protections that would otherwise invalidate reused credentials.
To detect this pattern, scans check whether authentication endpoints reissue or rotate tokens after login, whether tokens include unique identifiers, and whether the server validates token provenance. MiddleBrick’s API scans include checks against authentication and authorization misconfigurations that can enable fixation-like issues in JWT-based flows.
Jwt Tokens-Specific Remediation in Actix — concrete code fixes
Remediation focuses on ensuring that JWTs are issued only after successful authentication and that each token is bound to the authenticated context. Do not accept client-supplied tokens as a substitute for server-issued tokens. In Actix, implement the following patterns to reduce fixation risk.
Issue: Accepting client-provided tokens
Avoid allowing clients to dictate the token value. Instead, issue a fresh JWT upon successful login and store minimal session state server-side if needed. Never trust a token provided via query parameters or unvalidated headers as proof of identity.
Code example: Secure login issuing a JWT
use actix_web::{web, HttpResponse, Result};
use jsonwebtoken::{encode, Header, EncodingKey};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
jti: String, // unique token identifier
iat: usize,
exp: usize,
scopes: Vec,
}
async fn login(
credentials: web::Json,
data: web::Data<AppState>
) -> Result<HttpResponse> {
// Validate credentials against your user store
let user = validate_user(&credentials.username, &credentials.password).await?;
// Generate a unique token ID and bind to this authentication event
let token_id = uuid::Uuid::new_v4().to_string();
let now = chrono::Utc::now();
let claims = Claims {
sub: user.id.to_string(),
jti: token_id,
iat: now.timestamp() as usize,
exp: (now + chrono::Duration::minutes(30)).timestamp() as usize,
scopes: user.scopes.clone(),
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(data.config.jwt_secret.as_ref()),
)?;
// Return token to client; do not accept a token from the client
Ok(HttpResponse::Ok().json(serde_json::json!({ "access_token": token }))
}
Code example: Validating token provenance and claims
use actix_web::{dev::ServiceRequest, Error};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
fn validate_jwt(token: &str, secret: &[u8]) -> Result<Claims, jsonwebtoken::errors::Error> {
let mut validation = Validation::new(Algorithm::HS256);
validation.validate_exp = true;
validation.validate_nbf = true;
validation.validate_iat = true;
validation.required_spec_claims = std::collections::HashSet::from([
"iss".into(),
"sub".into(),
"jti".into(),
"exp".into(),
"iat".into(),
]);
validation.audience = Some(vec!["api.example.com".into()]);
validation.issuer = Some("auth.example.com".into());
let token_data = decode::(
token,
&DecodingKey::from_secret(secret),
&validation,
)?;
// Optionally check a server-side allowlist of jti if you need revocation
Ok(token_data.claims)
}
async fn authenticated_route(
req: ServiceRequest,
secret: web::Data<[u8]>
) -> Result<actix_web::HttpResponse, Error> {
let auth_header = req.headers().get("Authorization")
.ok_or_else(|| actix_web::error::ErrorUnauthorized("missing header"))?;
let token = auth_header.to_str()?.strip_prefix("Bearer ")
.ok_or_else(|| actix_web::error::ErrorBadRequest("invalid authorization format"))?;
let claims = validate_jwt(token, secret.as_ref())?;
// Ensure token is fresh for this route invocation; reject reused tokens if you track used jti
req.extensions_mut().insert(claims);
Ok(req.into_response(actix_web::HttpResponse::Ok().finish()))
}
Additional practices
- Set short expiration times and use the 'jti' claim to detect reuse.
- Bind tokens to the user’s IP or a session nonce where appropriate.
- Do not expose tokens in URLs; prefer headers or secure, HttpOnly cookies with SameSite and Secure flags.
- Validate issuer, audience, and algorithm strictly; avoid 'none' algorithms.
- Rotate secrets and implement token revocation strategies for compromised tokens.
These measures ensure that JWTs issued in Actix are tied to the authentication event and not subject to fixation via client-controlled values.