Clickjacking in Actix with Jwt Tokens
Clickjacking in Actix with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redress attack where an attacker tricks a user into interacting with a hidden or disguised element inside an invisible or overlay iframe. When JWT tokens are involved in an Actix-based application, the risk pattern often centers on embedding authenticated UI in iframes while relying on JWT tokens for authorization rather on strong anti-CSRF/anti-clickjacking controls. If an Actix route serves HTML that includes a JWT in an Authorization header but is embeddable in an external site, the browser will send the cookie-based session (if any) and may also include the JWT if it is exposed to JavaScript in the page, enabling the attacker to perform actions on behalf of the logged-in user within the hidden frame.
In practice, this can happen when an Actix endpoint returns a page with an authenticated iframe pointing to another Actix service, and the page either does not set Content-Security-Policy frame-ancestors, or sets a weak policy (e.g., allowing self or overly broad origins). Even if the Actix handler validates the JWT on each request, if there is no SameSite cookie policy and no anti-CSRF token for state-changing operations, a forged form inside the iframe can invoke authenticated endpoints. The JWT itself may be leaked via Referer headers, JavaScript errors, or insecure storage, and if the token is long-lived or improperly scoped, the impact is amplified. This combination is especially dangerous when the Actix app exposes admin or sensitive actions inside an iframe on a different origin that the attacker controls.
An illustrative scenario: an Actix frontend includes a dashboard widget loaded via an iframe from another subdomain, and the widget makes authenticated fetch calls that include a JWT in the Authorization header. If the subdomain does not enforce strict frame-ancestors and the JWT is accessible to JavaScript, an attacker can craft a page that overlays invisible controls over the widget. When the user clicks what they believe is a harmless element on the attacker’s page, they are actually submitting actions in the hidden iframe, and the browser attaches the JWT automatically, leading to unauthorized operations such as changing email or making purchases.
Jwt Tokens-Specific Remediation in Actix — concrete code fixes
Remediation centers on preventing the page from being embedded and ensuring JWT usage does not amplify clickjacking risk. Use strict Content-Security-Policy frame-ancestors to disallow embedding, apply SameSite and Secure cookie attributes for any session cookies, and avoid exposing JWTs in URLs or JavaScript-accessible storage when possible. For state-changing endpoints, require a CSRF token or use the SameSite cookie mode strictly, even when JWTs are used for authorization. The following code examples demonstrate secure patterns in Actix with JWT handling.
Example 1: Setting CSP frame-ancestors and secure JWT response headers
use actix_web::{web, App, HttpResponse, HttpServer, Responder, middleware::Logger};
use actix_web::http::header::{self, HeaderValue};
async fn protected_page() -> impl Responder {
let mut res = HttpResponse::Ok()
.content_type("text/html")
.insert_header((
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src 'self'; frame-ancestors 'none';"),
))
.insert_header((
header::X_CONTENT_TYPE_OPTIONS,
HeaderValue::from_static("nosniff"),
))
.insert_header((
header::X_FRAME_OPTIONS,
HeaderValue::from_static("DENY"),
))
.body(
r#"<html><body>Authenticated Dashboard</body></html>"#,
);
res
}
async fn api_with_jwt_auth(req: actix_web::HttpRequest) -> impl Responder {
// Expect JWT in Authorization: Bearer <token>
let auth = req.headers().get(header::AUTHORIZATION);
let token = match auth {
Some(v) => v.to_str().unwrap_or(""),
None => return HttpResponse::Unauthorized().finish(),
};
if !token.starts_with("Bearer ") {
return HttpResponse::BadRequest().body("Invalid authorization format");
}
let jwt = &token[7..];
// Validate jwt signature and claims here
HttpResponse::Ok().json(serde_json::json!({ "token_valid": true }))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(Logger::default())
.route("/dashboard", web::get().to(protected_page))
.route("/api/data", web::get().to(api_with_jwt_auth))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Example 2: Secure cookie-based session with SameSite and JWT in body-only responses
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use actix_session::{Session, storage::CookieSessionStore};
use actix_web::cookie::{time::Duration, SameSite};
async fn login(session: Session) -> impl Responder {
// Set session cookie with SameSite and Secure flags
session.insert("user", "alice").unwrap();
// Configure session cookie attributes externally via Actix session config in production
HttpResponse::Ok().body("Logged in")
}
async fn post_action(session: Session) -> impl Responder {
// Require session presence and CSRF token in body/header to defend against clickjacking
// Do not rely solely on JWT stored in JavaScript-accessible storage
HttpResponse::Ok().json(serde_json::json!({ "action": "executed" }))
}
// In your Actix App configuration (typically main or app factory):
// let cookie_secret = actix_web::cookie::Key::from(&[0; 32]);
// App::new()
// .wrap(Logger::default())
// .wrap(
// SessionMiddleware::builder(CookieSessionStore::default(), cookie_secret)
// .cookie_name("app_session".to_string())
// .cookie_secure(true)
// .cookie_http_only(true)
// .cookie_same_site(SameSite::Strict)
// .build()
// )
// .route("/action", web::post().to(post_action))
Best practices summary
- Set Content-Security-Policy: frame-ancestors 'none' (or strict origins) on authenticated pages.
- Use X-Frame-Options: DENY for legacy compatibility alongside CSP.
- Keep JWTs out of browser-accessible storage when possible; prefer HttpOnly, Secure, SameSite=Strict cookies for session linkage.
- Require anti-CSRF tokens for state-changing requests even when JWTs are used, to block cross-origin forged requests.
- Scope JWTs with short lifetimes and minimal claims to reduce impact if leaked via Referer or embedded contexts.