Cross Site Request Forgery in Axum with Mutual Tls
Cross Site Request Forgery in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability
Cross Site Request Forgery (CSRF) is an attack that tricks a victim into executing unwanted actions on a web application in which they are authenticated. In Axum, a Rust web framework, CSRF risk arises when state-changing endpoints rely only on cookies for session identification without additional anti-CSRF controls. Mutual Transport Layer Security (Mutual TLS) authenticates clients via mTLS certificates, which can give a false sense of security: mTLS ensures the client is who they claim to be, but it does not prevent a legitimate, authenticated client from being tricked into making an unwanted request to your API from another origin.
When you combine CSRF-prone endpoints with mTLS, the server may correctly validate the client certificate and still process the request because the TLS session proves client identity. However, the browser-based web context differs: if an Axum service serves web pages or JavaScript to browsers and relies on cookie-based session tokens (e.g., a session cookie set over HTTPS), those cookies will be included cross-origin by browsers unless explicitly protected. mTLS does not stop a browser from including those cookies in a forged request initiated by a malicious site. Therefore, CSRF remains possible in Axum when browser clients use cookie-based sessions, even if the backend uses mTLS for other clients (e.g., mobile or service-to-service). Attack patterns include malicious forms on external sites that perform POST, PUT, or DELETE against your Axum endpoints, leveraging the victim’s authenticated cookie state.
Crucially, mTLS does not protect against CSRF within browser contexts because CSRF is about unauthorized commands issued by an authenticated user’s browser, not about identity spoofing at the TLS layer. For Axum APIs consumed programmatically (non-browser clients), CSRF is typically not relevant because those clients manage their own request context and do not automatically include credentials cross-origin. The risk is highest when Axum serves both API and server-rendered views or JavaScript that makes authenticated requests, and when cookies are used for session management without SameSite, CSRF tokens, or equivalent protections.
Mutual Tls-Specific Remediation in Axum — concrete code fixes
To mitigate CSRF in Axum while using Mutual TLS, apply defense-in-depth: retain mTLS for strong client authentication, and add anti-CSRF protections tailored for browser sessions. For non-browser clients (e.g., services with mTLS), CSRF is generally not applicable, but you should still ensure idempotent designs and proper authorization checks. For browser-based sessions, use SameSite cookies, CSRF tokens, or validate the Origin/Referer headers where appropriate.
Below are concrete Axum examples demonstrating secure cookie settings and a basic CSRF token approach. These examples assume you use the axum, tower-cookies, and serde crates.
Example 1: Secure cookie attributes with SameSite and Secure
use axum::response::Response;
use axum::http::{HeaderMap, HeaderValue, SetCookie};
fn build_secure_cookie(cookie_value: &str) -> HeaderMap {
let mut headers = HeaderMap::new();
let cookie = format!("session={}; Path=/; Secure; HttpOnly; SameSite=Lax", cookie_value);
headers.insert(SetCookie::from_str(&cookie).unwrap().name(), HeaderValue::from_static("ignored"));
headers
}
// In your handler, attach the header to the response:
// let response = Response::builder().headers(build_secure_cookie("session-id")).body(...).unwrap();
Example 2: Double-submit cookie pattern with Axum
use axum::{routing::post, Router, extract::State, http::StatusCode};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tower_cookies::{Cookies, Cookie};
#[derive(Clone)]
struct AppState {
// In practice, use a secure random generator per request/session
csrf_secret: String,
}
async fn get_csrf_token(cookies: Cookies, State(state): State>) -> String {
let token = format!("{}.signed", state.csrf_secret); // simplistic; use HMAC in production
// Set a non-HttpOnly cookie so JavaScript can read it; Secure and SameSite are still enforced by browser.
let cookie = Cookie::build(("csrf_token", token.clone()))
.secure(true)
.http_only(false)
.same_site(axum::http::headers::SameSite::Lax)
.path("/")
.finish();
cookies.add(cookie);
token
}
async fn submit_data(
cookies: Cookies,
State(state): State>,
axum::extract::Form(payload): axum::extract::Form,
) -> Result {
let cookie_token = cookies.get("csrf_token")
.ok_or_else(|| (StatusCode::FORBIDDEN, "Missing CSRF token".into()))?
.value()
.to_string();
let form_token = payload["csrf_token"].as_str()
.ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing form field".into()))?;
if form_token != cookie_token {
return Err((StatusCode::FORBIDDEN, "CSRF token mismatch".into()));
}
// Proceed with state-changing logic
Ok("OK".into())
}
// Register routes:
// let app = Router::new()
// .route("/csrf-token", get(get_csrf_token))
// .route("/submit", post(submit_data));
Example 3: mTLS-aware handler with authorization checks
use axum::{routing::get, Router, extract::State};
use axum::http::HeaderMap;
async fn sensitive_operation(
headers: HeaderMap,
State(state): State>,
) -> Result {
// Verify mTLS client cert details via request extensions (configured by your TLS layer)
// Perform additional authorization checks, e.g., verify claims, scopes, or roles
// This is complementary to mTLS and does not replace CSRF protections for browser flows.
Ok("success".into())
}
// let app = Router::new().route("/sensitive", get(sensitive_operation));
In summary, mTLS in Axum provides robust channel and client authentication but does not prevent CSRF in browser-based sessions. Combine mTLS with SameSite/Secure cookies, CSRF tokens, and origin verification to achieve comprehensive protection.