Token Replay in Actix
Token Replay in Actix Web APIs
Token replay occurs when an authenticated request containing a valid session token is resent without modification, allowing an attacker to reuse previously captured credentials or authorization artifacts against the same endpoint. In Actix Web, this commonly happens with JWT-based authentication flows where the Authorization header or cookie-based session identifiers are not bound to request-specific context such as IP address, user agent, or request timestamp.
Typical attack vectors include:
- Reusing a captured
Authorization: Bearer <token>header across multiple requests to protected routes like/api/v1/user/profileor/api/v1/orderswithout server-side invalidation. - Exploiting cookie-based sessions where the
session_idcookie is replayed from a different network location or device, bypassing intended session binding. - Failing to enforce replay protection for state-changing endpoints (e.g., POST to
/api/v1/payments) that rely solely on static tokens without nonce or timestamp validation.
In Actix Web, token replay often manifests in endpoints protected by actix-web-http-auth or custom JWT middleware that validate token signature and expiration but do not invalidate tokens after first use. Attackers can capture valid requests using tools like curl or Burp Suite and replay them directly, bypassing intended authorization checks. Because Actix Web treats each request as stateless by default, there is no inherent replay protection unless explicitly implemented by the developer.
The risk is heightened when tokens are long-lived, lack binding to context, or are transmitted over unencrypted channels. This violates the principle of single-use tokens and can lead to unauthorized data access, privilege escalation, or financial transaction abuse if replay targets sensitive operations.
Detecting Token Replay in Actix Web with middleBrick
middleBrick identifies token replay risks by analyzing authentication flows across Actix Web endpoints during unauthenticated black-box scanning. When scanning an endpoint like POST /api/v1/transactions, middleBrick sends a series of authenticated-looking requests using previously captured valid tokens (simulated via test tokens) and checks for consistency in server behavior.
Key detection signals include:
- Identical response codes and body content when the same token is reused across multiple requests to state-changing endpoints.
- Lack of server-side token invalidation or single-use enforcement in the response headers or JSON payload.
- Token validation occurring without associated context binding (e.g., no
X-Request-IDcorrelation or IP check in responses).
middleBrick performs these checks in parallel with its 12-scan suite, marking token replay as a BOLA/IDOR or Privilege Escalation finding depending on the endpoint's access control logic. The scanner evaluates whether the server properly distinguishes between legitimate reuse (e.g., session persistence) and suspicious replay (e.g., identical token across geographically distinct IPs or rapid successive calls). Findings include severity ratings, OWASP Top 10 mapping (A01:2021 Broken Access Control), and remediation guidance.
Example scan output snippet:
Findings:
- Token Replay Detected on POST /api/v1/transactions
- Severity: High
- Category: BOLA/IDOR
- Description: Token reused without context binding or invalidation
- Recommendation: Implement single-use tokens or bind tokens to request contextRemediating Token Replay in Actix Web
To mitigate token replay in Actix Web, developers should implement server-side token invalidation and context binding using one-time-use tokens or short-lived credentials with nonce enforcement. The following code demonstrates a secure pattern using Actix Web's middleware and JWT validation with replay protection.
use actix_web::{web, App, HttpServer, HttpResponse, Responder};
use jsonwebtoken::{encode, decode, Validation, DecodingKey};
use std::collections::HashSet;
// Track used tokens (in practice, use Redis or DB)
let mut used_tokens: HashSet<String> = HashSet::new();
async fn protected_route(req: web::HttpRequest, token: web::Json<String>) -> impl Responder {
let token_str = token.0.clone();
let mut validation = Validation::default();
validation.validate_exp = true;
let token_data = match decode(&token_str, &DecodingKey::from_secret(&[0; 32]), &validation) {
Ok(data) => data.claims,
Err(_) => return HttpResponse::Unauthorized().finish(),
};
// Bind token to request context (e.g., IP, user agent, timestamp)
let client_ip = req.connection_info().remote_addr().unwrap_or("unknown");
let binding_key = format!("{}_{}", token_str, client_ip);
// Enforce single-use token
if !used_tokens.insert(binding_key.clone()) {
return HttpResponse::Forbidden().body("Token already used");
}
// Optional: Set short expiration and track usage
HttpResponse::Ok().json(serde_json::json!({
"user": token_data.sub,
"used_at": chrono::Utc::now()
}))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().route("/api/v1/transactions", web::post().to(protected_route)))
.bind((