Session Fixation in Actix with Bearer Tokens
Session Fixation in Actix with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Session fixation occurs when an application assigns a user a session identifier before authentication and does not issue a new identifier after authentication. In Actix web applications that rely on Bearer Tokens for authentication, fixation can arise when a pre‑existing token is accepted both before and after login without rotation or validation changes. For example, if an API accepts an Authorization: Bearer
In Actix, this can surface when a handler or guard checks a Bearer token extracted from headers but does not validate that the token’s subject or session binding changes after login. If token issuance and validation logic does not incorporate per‑user claims binding (such as user ID or session nonce) and does not reject tokens issued prior to authentication, the application may allow privilege confusion across authentication boundaries. This becomes especially relevant when tokens carry user identifiers but are not re‑validated against a current authentication state, enabling an attacker to leverage a known token to access protected endpoints after the victim authenticates.
Consider an Actix service that authenticates requests by checking a Bearer token against an introspection endpoint or a local key. If a route like /api/me relies solely on the presence of a valid Bearer token and does not ensure that the token was issued after successful authentication, an attacker can fix a token in the victim’s browser (for instance, by persuading the victim to visit https://api.example.com/authorize?token=ATTACKER_TOKEN) and later trigger authenticated requests using that same token. Because the token remains valid and the server does not enforce a fresh authentication context, the server may process the request as if the victim authenticated with the attacker’s token, leading to unauthorized data access or actions performed under the victim’s identity.
Real-world parallels include scenarios where access tokens issued before login remain accepted after login, as seen in certain OAuth flows when refresh tokens or access tokens are not rotated or bound to session state. The OWASP API Security Top 10 highlights broken object level authorization and security misconfiguration as common classes that can intersect with fixation risks when token validation is incomplete. Effective mitigation in Actix requires that authentication handlers issue or rotate tokens after login, bind tokens to user-specific claims, and enforce validation that ties token validity to the authenticated session rather than the pre‑auth token alone.
Bearer Tokens-Specific Remediation in Actix — concrete code fixes
Remediation focuses on ensuring that Bearer token validation changes after authentication and that tokens are bound to the authenticated user. Below are concrete Actix patterns that demonstrate secure handling.
- Require token rotation and validate user binding after login
use actix_web::{web, HttpRequest, HttpResponse, Responder};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation, TokenData};
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct Claims {
sub: String, // subject/user identifier
exp: usize, // expiration
jti: String, // JWT ID for replay/session tracking
// include additional per‑session or per‑request nonce if needed
}
async fn validate_token(auth_header: Option<&str>, key: &DecodingKey) -> Result, &'static str> {
let token = auth_header.and_then(|h| h.strip_prefix("Bearer ")).ok_or("Missing or invalid Authorization header")?;
let mut validation = Validation::new(Algorithm::HS256);
validation.validate_exp = true;
// enforce issuer/audience checks in production
decode::(token, key, &validation).map_err(|_| "Invalid token")
}
// Handler for login: issue a new token and avoid reusing the pre‑auth token
async fn login(
credentials: web::Json,
key: web::Data,
) -> impl Responder {
// authenticate credentials against your user store
if credentials.username == "ok" && credentials.password == "ok" {
let claims = Claims {
sub: credentials.username.clone(),
exp: (chrono::Utc::now() + chrono::Duration::hours(1)).timestamp() as usize,
jti: uuid::Uuid::new_v4().to_string(),
};
let token = encode(&Header::default(), &claims, &key).map_err(|_| "Token creation failed");
HttpResponse::Ok().json(serde_json::json!({ "access_token": token.ok() }))
} else {
HttpResponse::Unauthorized().finish()
}
}
// Protected handler: validate token and ensure it matches the authenticated user context
async fn me(req: HttpRequest, key: web::Data) -> impl Responder {
match validate_token(req.headers().get("authorization").and_then(|v| v.to_str().ok()), &key) {
Ok(token_data) => HttpResponse::Ok().json(serde_json::json!({ "user": token_data.claims.sub })),
Err(_) => HttpResponse::Unauthorized().finish(),
}
} - Reject pre‑auth tokens on authenticated routes
In addition to issuing new tokens, ensure authenticated endpoints reject tokens that were issued before login. One approach is to maintain a per‑user token revocation or session marker (e.g., a last‑password‑change timestamp or a nonce stored server‑side or in claims). The validation logic should check that the token’s jti or a custom session claim is consistent with the current session state.
async fn validate_token_with_session(
auth_header: Option<&str>,
key: &DecodingKey,
user_session_nonce: &str, // e.g., retrieved from DB/cache after login
) -> Result {
let token = auth_header.and_then(|h| h.strip_prefix("Bearer ")).ok_or("Missing Authorization")?;
let mut validation = Validation::new(Algorithm::HS256);
validation.validate_exp = true;
let token_data: TokenData = decode(token, key, &validation).map_err(|_| "Invalid token")?;
// ensure token is bound to the current session
if token_data.claims.nonce != Some(user_session_nonce.to_string()) {
return Err("Token not bound to current session");
}
Ok(token_data.claims)
} - Enforce HTTPS and avoid token leakage in URLs
Actix middleware can strip or reject tokens passed in query parameters to prevent leakage in logs or browser history. Use request guards to ensure only the Authorization header is accepted.
use actix_web::dev::ServiceRequest; use actix_web::Error; use actix_web_httpauth::extractors::bearer::BearerAuth; async fn bearer_auth_wrapper(req: ServiceRequest) -> Result{ let auth = req.headers().get("authorization"); if auth.and_then(|v| v.to_str().ok()).map_or(false, |v| v.starts_with("Bearer ")) { // allow only Authorization header, reject query param tokens req.extensions_mut().insert(BearerAuth::borrowed(auth.unwrap().to_str().unwrap().trim_start_matches("Bearer ").trim())); Ok(req) } else { Err(actix_web::error::ErrorUnauthorized("Bearer token required in Authorization header")) } }
These patterns demonstrate how Actix services can bind Bearer tokens to post‑login state, rotate tokens, and reject fixation by ensuring that authentication revalidation produces new token material and ties it to the user’s session.