HIGH cross site request forgeryaxumapi keys

Cross Site Request Forgery in Axum with Api Keys

Cross Site Request Forgery in Axum with Api Keys — how this specific combination creates or exposes the vulnerability

Cross Site Request Forgery (CSRF) occurs when an attacker tricks a victim’s browser into making an unwanted authenticated request to a target application. In Axum, using only API keys for authentication without additional protections can inadvertently expose endpoints to CSRF-like abuse in browser-based contexts. API keys are typically stored in headers and are not automatically sent by browsers in cross-origin requests the same way cookies are, but if API keys are embedded in JavaScript, stored in local storage, or leaked via a referrer, a malicious site can craft requests that include the key via headers or query parameters if the client-side code does not properly isolate origins.

Axum applications that rely solely on API keys for authorization and do not implement anti-CSRF measures such as SameSite cookies, CSRF tokens, or strict CORS policies may be vulnerable when the key is accessible to browser-side code. For example, a frontend JavaScript application that reads an API key from a config file and sends it in an Authorization: Bearer <key> header can expose that key to cross-origin requests if CORS is misconfigured. An attacker can then lure a logged-in user to a malicious page that issues requests with the stolen key, performing actions on behalf of the user. This is particularly relevant when API keys are used for authentication rather than scoped tokens with limited lifetimes, and when endpoints accept both cookie-based sessions and header-based keys without validating origin or using strict referrer checks.

Consider an Axum handler that accepts an API key via a custom header and performs sensitive operations such as changing an email or initiating a transfer. If the application does not validate the Origin or Referer headers and does not require a CSRF token for state-changing methods, an attacker can construct a form or script on another domain that invokes this endpoint. Because the browser includes cookies and, if improperly handled, custom headers in cross-origin requests preflighted by CORS, the API key may be exposed or the request may be executed unintentionally. The risk is compounded when CORS is permissive (*) or when credentials are allowed without explicit origins, enabling attackers to leverage authenticated sessions indirectly.

Middleware and route design in Axum must differentiate between trusted server-to-server calls and browser-originated requests. Relying on API keys alone without binding them to a specific origin, using short-lived tokens, or enforcing strict CORS rules increases the attack surface. Security checks performed by scanners like middleBrick include CORS configuration and header validation as part of the Property Authorization and Input Validation checks, highlighting whether endpoints properly reject cross-origin requests or lack CSRF mitigations. Developers should treat API keys as sensitive credentials and ensure they are not embedded in JavaScript bundles or exposed via logs, leveraging secure storage and transmission practices to reduce CSRF and related injection risks.

Api Keys-Specific Remediation in Axum — concrete code fixes

Remediation focuses on ensuring API keys are not usable in cross-origin browser contexts and that Axum endpoints validate requests rigorously. Use server-side storage for keys, avoid exposing them to clients, and enforce strict CORS and CSRF protections. Below are concrete Axum examples demonstrating secure handling.

First, store API keys server-side (e.g., in environment variables or a secure vault) and validate them via middleware without exposing them to the frontend. Use Axum middleware to extract and verify keys from headers, rejecting requests that do not meet origin and key criteria.

use axum::{routing::post, Router, extract::Request, middleware::Next, http::HeaderMap};
use std::net::SocketAddr;
use std::sync::Arc;

struct ApiKeyValidator {
    valid_key: String,
    allowed_origin: String,
}

impl ApiKeyValidator {
    fn new(valid_key: String, allowed_origin: String) -> Self {
        Self { valid_key, allowed_origin }
    }

    async fn validate(&self, req: Request, next: Next) -> axum::response::Response {
        // Validate Origin header to mitigate CSRF
        let origin = req.headers().get("Origin")
            .and_then(|v| v.to_str().ok())
            .unwrap_or("");
        if origin != self.allowed_origin {
            return axum::response::Response::builder()
                .status(403)
                .body("Forbidden".into())
                .unwrap();
        }

        // Validate API key in Authorization header
        let auth = req.headers().get("Authorization")
            .and_then(|v| v.to_str().ok())
            .map(|s| s.strip_prefix("Bearer ").unwrap_or(s));
        match auth {
            Some(key) if key == self.valid_key => next.run(req).await,
            _ => axum::response::Response::builder()
                .status(401)
                .body("Unauthorized".into())
                .unwrap(),
        }
    }
}

#[tokio::main]
async fn main() {
    let validator = Arc::new(ApiKeyValidator::new(
        std::env::var("API_KEY").expect("API_KEY must be set"),
        std::env::var("ALLOWED_ORIGIN").expect("ALLOWED_ORIGIN must be set"),
    ));

    let app = Router::new()
        .route("/secure", post(|_| async { "OK" }))
        .layer(axum::middleware::from_fn_with_state(
            validator.clone(),
            move |req, next| {
                let validator = validator.clone();
                async move { validator.validate(req, next).await }
            },
        ));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

Second, enforce CORS strictly to prevent cross-origin misuse. Configure CORS to allow only specific origins and do not permit credentials universally. This ensures that even if an API key is somehow exposed, browsers will not send it to unauthorized domains.

use axum::routing::post;
use tower_http::cors::{CorsLayer, Any};

let cors = CorsLayer::new()
    .allow_origin("https://trusted.example.com".parse().unwrap())
    .allow_methods(Any)
    .allow_headers(Any)
    .allow_credentials(false); // Do not allow credentials from any origin

let app = Router::new()
    .route("/action", post(|_| async { "Executed" }))
    .layer(cors);

Third, avoid using API keys in client-side code. When server-side rendering or single-page applications require keys for backend calls, proxy requests through your own backend rather than exposing keys directly. This eliminates the risk of key theft via browser inspection or cross-origin requests. Combine these practices with regular rotation of keys and scope them to least privilege to reduce impact if compromised. middleBrick’s scans can validate these configurations by checking CORS settings and whether API keys are discoverable in client-accessible resources.

Frequently Asked Questions

Does using API keys in Axum automatically prevent CSRF?
No. API keys do not prevent CSRF when exposed to browsers. Without SameSite attributes, CSRF tokens, or strict CORS, attackers can craft requests that include the key if it is accessible via headers or JavaScript, making server-side origin validation essential.
How can I verify my Axum API key implementation is CSRF-resistant?
Ensure API keys are stored server-side, validate Origin and Referer headers on each request, enforce strict CORS without wildcards or allow-credentials, and avoid exposing keys to frontend code. Tools like middleBrick can scan your endpoints to confirm CORS and header validation configurations are not permissive.