Information Disclosure in Actix with Jwt Tokens
Information Disclosure in Actix with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Information disclosure in Actix applications that use JWT tokens typically occurs when token handling, serialization, or validation logic unintentionally exposes sensitive claims, keys, or internal state. In Actix, routes and extractors often deserialize incoming tokens into Rust structs; if those structs or debug representations include fields such as secret, kid, or raw key material, and responses or errors include them (for example via logging or debug error messages), tokens or their contents may be disclosed to clients or logs.
A common pattern is defining a claims struct with a secret field and then using serde to deserialize the token payload. If the application echoes the decoded payload in error responses or includes the struct in logs, sensitive data can leak:
use actix_web::{web, HttpResponse, Error};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
struct Claims {
sub: String,
exp: usize,
secret: String, // sensitive field that should never be returned to the client
}
async fn validate_token(token: web::Json) -> Result {
// If validation fails and you return token data, you risk disclosure
Ok(HttpResponse::Ok().json(token.into_inner()))
}
In this example, returning the Claims struct (including secret) can disclose sensitive token payload data. Similarly, using #[derive(Debug)] on token-related structs and exposing those debug representations in logs or trace output can lead to information leakage in logs or error traces seen by clients.
Another scenario involves failing to validate the token’s audience or issuer and reflecting the raw token or its header in error responses. For example, returning the token string or its header when a signature verification fails can expose encoded data that may contain usernames, roles, or other sensitive metadata.
Middleware or guards that parse tokens should avoid including token material in responses or logs. In Actix, extractors can be implemented to return a sanitized authentication context instead of the raw token. Additionally, care must be taken to ensure that token validation errors do not include details such as which component failed, the expected vs actual signature, or partial token contents, which can aid an attacker in refining injection or tampering attempts.
Real-world attack patterns include an attacker sending a malformed token and observing verbose error messages that include the token header or payload. Such messages may reference constants like kid or include base64 fragments that should remain opaque. The presence of sensitive claims in structured logging also means that aggregated logs can become an unintended data exposure channel.
To align with checks performed by tools such as middleBrick, which runs checks including Data Exposure and tests for issues like reflected token data or verbose error disclosure, developers should ensure that token parsing logic never reflects raw token bytes or structured claims in HTTP responses. Using middleBrick’s scans, teams can detect whether API error responses inadvertently include token-like values or sensitive fields, and remediate by tightening error handling and sanitizing logs.
Jwt Tokens-Specific Remediation in Actix — concrete code fixes
Remediation focuses on ensuring tokens are validated without echoing sensitive data, avoiding debug leaks, and returning minimal, safe authentication context. Below are concrete code fixes for Actix applications.
1. Do not return token payloads in responses
Instead of echoing the token or its claims, return only a minimal authenticated context (e.g., user ID or roles) after successful validation.
use actix_web::{web, HttpResponse};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
struct Claims {
sub: String,
exp: usize,
role: String,
// Do NOT include sensitive fields like secret here
}
#[derive(Debug, Serialize)]
struct AuthContext {
user_id: String,
role: String,
}
async fn validate_token(token: web::Json) -> HttpResponse {
// Avoid using the incoming token in the response
HttpResponse::BadRequest().json("Do not echo token payloads")
}
async fn verify_and_issue_context(
token: String,
) -> Result {
let decoding_key = DecodingKey::from_secret("secret".as_ref());
let validation = Validation::new(Algorithm::HS256);
let token_data = decode::(&token, &decoding_key, &validation)
.map_err(|e| actix_web::error::ErrorUnauthorized(e.to_string()))?;
// Return only safe, minimal information
let context = AuthContext {
user_id: token_data.claims.sub,
role: token_data.claims.role,
};
Ok(HttpResponse::Ok().json(context))
}
2. Avoid logging or exposing debug info for tokens
Ensure token structs do not derive Debug in a way that could be logged inadvertently, and sanitize logs to exclude token material.
use actix_web::dev::ServiceRequest;
use actix_web_httpauth::extractors::bearer::BearerAuth;
use log::info;
async fn route_with_logging(req: ServiceRequest) -> actix_web::Result {
// Extract token without logging its value
let auth = req.extensions().get::().ok_or_else(|| {
actix_web::error::ErrorUnauthorized("Missing authorization")
})?;
// Log only non-sensitive identifiers, never the token itself
info!(user = auth.user_id(), method = req.method().as_str());
Ok(req)
}
3. Return generic errors on token validation failure
Do not disclose whether a signature mismatch, expired token, or malformed token was the cause. Use a uniform error response.
use actix_web::{error, HttpResponse};
use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode};
async fn verify_token(token: String) -> Result, actix_web::Error> {
let decoding_key = DecodingKey::from_secret("secret".as_ref());
let mut validation = Validation::new(Algorithm::HS256);
// Do not customize error output to reflect internal validation steps
decode(&token, &decoding_key, &validation)
.map_err(|_| error::ErrorUnauthorized("Invalid authentication token"))
}
4. Validate audience and issuer to prevent token confusion
Always validate claims such as aud and iss to ensure tokens intended for your service are not accepted from other audiences.
use jsonwebtoken::{Algorithm, DecodingKey, Validation, TokenData};
fn build_validation() -> Validation {
let mut validation = Validation::new(Algorithm::HS256);
validation.validate_aud = true;
validation.validate_iss = true;
validation.set_audience(&["my-api-audience"]);
validation.set_issuer(&["https://auth.example.com"]);
validation
}
async fn verify_with_full_validation(token: String) -> Result, actix_web::Error> {
let decoding_key = DecodingKey::from_secret("secret".as_ref());
let validation = build_validation();
decode(&token, &decoding_key, &validation)
.map_err(|_| error::ErrorUnauthorized("Invalid token"))
}
5. Use strong key management and avoid hardcoded secrets
Use environment variables or secret stores for keys instead of embedding them in source code.
use actix_web::web;
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
use std::env;
async fn secure_key_verify(token: String) -> Result, actix_web::Error> {
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
let decoding_key = DecodingKey::from_secret(secret.as_ref());
let mut validation = Validation::new(Algorithm::HS256);
validation.set_audience(&["my-api-audience"]);
decode(&token, &decoding_key, &validation)
.map_err(|_| actix_web::error::ErrorUnauthorized("Invalid token"))
}