Adding CORS Support to Rust Axum Applications

Last updated: April 13, 2025

1. Introduction: CORS and Axum

When developing APIs with Axum in Rust, enabling Cross-Origin Resource Sharing (CORS) is a common requirement to allow frontend applications served from different domains to interact with your API. Without proper CORS headers configured on the server, browsers will block these cross-origin requests due to the Same-Origin Policy.

Axum leverages the Tower ecosystem for middleware. CORS handling is typically achieved using the CorsLayer provided by the tower-http crate. This guide demonstrates how to integrate and configure CorsLayer in your Axum application.

We assume you have a basic Axum application structure, as shown in our Building a Simple Web API with Rust and Axum tutorial.

2. What is CORS? (Quick Recap)

Cross-Origin Resource Sharing (CORS) is a browser security feature that controls how web pages from one origin can request resources from a different origin. It works via HTTP headers sent by the server. If the server sends the correct headers (like Access-Control-Allow-Origin), the browser permits the request; otherwise, it blocks it.

Remember, CORS is enforced by the browser, and the fix always involves configuring the server to send the appropriate response headers. For more details, refer to Understanding CORS Errors and How to Fix Them.

3. Using tower-http and CorsLayer

The recommended way to handle CORS in Axum is via the CorsLayer middleware from the tower-http crate. tower-http provides various useful middleware implementations (like tracing, compression, CORS) that integrate seamlessly with Axum and the Tokio ecosystem.

3.1 Installation

Add tower-http to your Cargo.toml dependencies, ensuring you enable the "cors" feature flag:

[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Add tower-http with the "cors" feature
tower = "0.4" # tower is often needed implicitly by tower-http or axum features
tower-http = { version = "0.5", features = ["cors"] } # Check crates.io for latest version

Or use cargo add:

cargo add tower-http --features cors
cargo add tower # Might be needed if not pulled in automatically

3.2 Basic Usage (Allowing Specific Origins)

You configure CORS by creating a CorsLayer instance and specifying the allowed origins, methods, etc.

use tower_http::cors::CorsLayer;
use axum::http::{Method, header}; // Import Method and header constants

// Example: Allow GET and POST from a specific origin
let cors = CorsLayer::new()
    .allow_origin("https://myfrontend.com".parse::().unwrap()) // Allow a single origin
    .allow_methods([Method::GET, Method::POST]) // Allow specific methods
    .allow_headers([header::CONTENT_TYPE]); // Allow specific headers
    // .allow_credentials(true) // Uncomment if credentials (cookies) are needed

// This `cors` layer will be added to the Axum app later

Note: We use .parse::().unwrap() for simplicity here. In production, handle potential parsing errors gracefully.

3.3 Allowing Any Origin (Use Carefully)

To allow requests from any origin (often needed for public APIs or during early development), use .allow_origin(Any). Warning: Be cautious with this in production, especially if credentials or sensitive data are involved.

use tower_http::cors::{CorsLayer, Any}; // Import Any

let cors_permissive = CorsLayer::new()
    .allow_origin(Any) // Allow any origin (wildcard *)
    .allow_methods(Any) // Allow any method
    .allow_headers(Any); // Allow any header
    // .allow_credentials(true) // IMPORTANT: Cannot use Any origin with credentials!

// Use this layer carefully!

Remember, you cannot allow Any origin if .allow_credentials(true) is set.

3.4 Detailed Configuration

CorsLayer offers a fluent builder pattern for detailed configuration:

use axum::http::{header, Method, HeaderValue};
use tower_http::cors::{CorsLayer, AllowedOrigins, Any};
use std::time::Duration;


let allowed_origins = AllowedOrigins::some_exact(&[
    "https://your-frontend.com".parse().unwrap(),
    "http://localhost:3000".parse().unwrap(), // Dev origin
]);

let cors_layer = CorsLayer::new()
    // Origins allowed to make requests
    .allow_origin(allowed_origins)
    // Set allowed HTTP methods
    .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
    // Set allowed request headers
    .allow_headers([
        header::AUTHORIZATION,
        header::ACCEPT,
        header::CONTENT_TYPE,
    ])
    // Allow credentials (cookies, authorization headers)
    .allow_credentials(true) // Must use specific origins, not Any
    // Optionally expose headers in the response
    .expose_headers([header::LINK])
    // Optionally set max age for preflight requests (in seconds)
    .max_age(Duration::from_secs(3600)); // Cache preflight for 1 hour

// This layer is now ready to be added to the Axum Router

4. Applying the Middleware

Middleware in Axum (and Tower) is applied using layers. You add the configured CorsLayer to your Router using the .layer() method.

use axum::{routing::get, Router};
use std::net::SocketAddr;
use tower_http::cors::{CorsLayer, Any}; // Use appropriate imports

// ... (Define your handlers like root_handler, etc.) ...
async fn root_handler() -> &'static str { "Hello!" }

#[tokio::main]
async fn main() {
    // Configure CORS layer (example: permissive for development)
    let cors = CorsLayer::new()
        .allow_origin(Any) // Be careful in production!
        .allow_methods(Any)
        .allow_headers(Any);

    // Build application router
    let app = Router::new()
        .route("/", get(root_handler))
        // ... add other routes ...
        .layer(cors); // Apply the CORS middleware layer to all routes

    // Run the server
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    println!("🚀 Server listening on {}", addr);
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

The .layer(cors) call applies the CORS handling logic to all routes defined *before* it in the router chain (or all routes if applied last). The middleware intercepts requests, handles preflight (OPTIONS) requests, and adds the necessary Access-Control-Allow-* headers to responses.

5. Complete Example

Combining a simple route with a specific CORS configuration:

use axum::{routing::get, Router, Json};
use std::net::SocketAddr;
use tower_http::cors::{CorsLayer, AllowedOrigins};
use axum::http::{Method, HeaderValue, header};
use serde::Serialize;

#[derive(Serialize)]
struct DataResponse {
    message: String,
}

async fn data_handler() -> Json {
    Json(DataResponse { message: "Data fetched successfully with CORS!".to_string() })
}

#[tokio::main]
async fn main() {
    // Configure CORS for specific origins and methods
    let allowed_origins = AllowedOrigins::some_exact(&[
        "http://localhost:5173".parse().unwrap(), // Common port for Vite dev server
        "https://my-prod-frontend.com".parse().unwrap(),
    ]);

    let cors = CorsLayer::new()
        .allow_origin(allowed_origins)
        .allow_methods([Method::GET, Method::POST]) // Allow GET and POST
        .allow_headers([header::CONTENT_TYPE]) // Allow Content-Type header
        .allow_credentials(false); // Example: No credentials needed


    // Build application router
    let app = Router::new()
        .route("/api/data", get(data_handler))
        .layer(cors); // Apply CORS middleware

    // Run the server
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    println!("🚀 Axum server with CORS listening on {}", addr);
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

6. Conclusion

Adding CORS support to Axum applications is straightforward using the CorsLayer middleware from the tower-http crate. By configuring this layer appropriately, you can control which origins, methods, and headers are permitted for cross-origin requests, enabling secure communication between your frontend and backend.

Always prioritize security by configuring the allowed origins and methods as strictly as possible for your use case, especially avoiding wildcard origins (*) when credentials are required.

7. Additional Resources

Related Articles

External Resources