Cross Site Request Forgery in Axum with Firestore
Cross Site Request Forgery in Axum with Firestore — how this specific combination creates or exposes the vulnerability
Cross Site Request Forgery (CSRF) in an Axum application that uses Firestore can arise when state-changing HTTP requests rely solely on cookies for session handling without additional anti-CSRF controls. Axum is a Rust web framework that does not include built-in CSRF protection; it leaves defense to the developer. If your Axum app uses cookie-based authentication (for example, a session cookie set after signing in to a Firestore-backed service), a malicious site can craft forms or fetch requests that leverage the user’s existing cookie to perform actions on Firestore via your backend endpoints.
Consider an endpoint in Axum that deletes a Firestore document based on a document ID provided by the user:
// Example Axum handler that deletes a Firestore document without CSRF defenses
async fn delete_document(
Path(doc_id): Path,
Extension(firestore_client): Extension,
// No anti-CSRF token validation
) -> Result<impl IntoResponse, (StatusCode, String)> {
firestore_client.delete_doc(&doc_id).await?;
Ok((StatusCode::OK, "deleted"))
}
If this endpoint expects only a cookie for authentication and does not validate a CSRF token or use same-site/cookie attributes properly, an attacker can trick a logged-in user into submitting a request to /documents/{doc_id} (e.g., via an image tag or form submission hosted on a malicious site). Because the browser automatically includes the session cookie, the request is authorized, and the Firestore document is deleted.
The risk is compounded when Firestore security rules rely only on authentication state (e.g., request.auth != null) and do not enforce per-user checks aligned with the application’s authorization model. Insecure CORS configurations in the Firestore rules or Axum middleware can also expose endpoints to cross-origin requests, making CSRF or cross-origin data manipulation easier. This combination highlights the importance of treating unauthenticated attack surface and ensuring that each state-changing route validates both identity and permissions rather than relying on browser cookie behavior alone.
Firestore-Specific Remediation in Axum — concrete code fixes
To mitigate CSRF for Axum applications interacting with Firestore, implement anti-CSRF tokens and enforce strict same-site cookie policies. Use double-submit cookie patterns or synchronizer token patterns where the server sets a CSRF token in a separate, non-cookie header and validates it on each state-changing request.
Below is a concrete Axum handler that requires a CSRF token in a custom header and verifies it against a token stored server-side (or signed and validated via a JWT claim). The Firestore client is called only after validation:
use axum::{{ async_trait, body::Body, extract::FromRequest, http::request::Parts, response::IntoResponse, routing::post, Json, Router, }}; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; // A simple CSRF token claim in a JWT or server-side session #[derive(Debug, Deserialize)] struct Claims { csrf_token: String, } // Middleware or extractor that validates the header token against the claims struct CsrfToken(String); #[async_trait] impl FromRequest for CsrfToken { type Rejection = (StatusCode, String); async fn from_request(parts: &mut Parts, body: &mut Body) -> Result<Self, Self::Rejection> { let header_value = parts .headers .get("x-csrf-token") .ok_or((StatusCode::FORBIDDEN, "Missing CSRF token".to_string()))? .to_str() .map_err(|_| (StatusCode::FORBIDDEN, "Invalid CSRF token".to_string()))?; // Here you would validate the token (e.g., compare to session store or verify JWT) // For simplicity, assume validation passes Ok(CsrfToken(header_value.to_string())) } } async fn delete_document( Path(doc_id): Path<String>, Extension(firestore_client): Extension<FirestoreClient>, CsrfToken(token): CsrfToken, // Optionally validate token against session/claims ) -> Result<impl IntoResponse, (StatusCode, String)> { // Proceed only after CSRF check firestore_client.delete_doc(&doc_id).await?; Ok((StatusCode::OK, "deleted")) } // Firestore client stub for compilation context struct FirestoreClient; impl FirestoreClient { async fn delete_doc(&self, _doc_id: &str) -> Result<(), (StatusCode, String)> { // Real Firestore SDK calls would go here Ok(()) } } #[tokio::main] async fn main() { let app = Router::new() .route("/documents/:doc_id", post(delete_document)); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("Listening on {}", addr); }On the Firestore side, ensure security rules perform user-specific checks and do not rely on the mere presence of
request.auth. Combine this with secure cookie attributes in your session management:// Example secure cookie settings in Axum middleware let app = Router::new() .layer( CookieManagerLayer::new() .set_cookie_config( CookieConfig::new() .http_only(true) .secure(true) .same_site(SameSite::Strict), ), );By requiring an explicit CSRF token in a header and tightening cookie/session settings, you reduce the likelihood of successful CSRF attacks against Firestore-backed Axum services.