Adding CORS Support to Rust Actix Web Applications

Last updated: April 13, 2025

1. Introduction: CORS in Actix Web

When building APIs with Actix Web, a common requirement is enabling Cross-Origin Resource Sharing (CORS) to allow frontend applications hosted on different domains to make requests to your API. Browsers block such requests by default due to the Same-Origin Policy unless the server explicitly permits them via specific HTTP headers.

Actix Web integrates well with middleware for handling cross-cutting concerns like CORS. The official recommendation and most common approach is to use the actix-cors crate. This guide shows how to install, configure, and apply the actix-cors middleware to your Actix Web application.

We assume you have a basic Actix Web setup, similar to the one described in Building a Simple Web API with Rust and Actix Web.

2. What is CORS? (Quick Recap)

Cross-Origin Resource Sharing (CORS) is a browser security feature using HTTP headers to manage access to resources from different origins (domain, scheme, port). Servers send headers like Access-Control-Allow-Origin to tell browsers which external origins are permitted to access their resources. If the headers aren't correctly configured for a cross-origin request, the browser blocks it.

Fixing CORS issues always involves configuring the server. For a detailed explanation, see Understanding CORS Errors and How to Fix Them.

3. Using the actix-cors Crate

The actix-cors crate provides middleware for Actix Web that handles the logic of checking CORS preflight requests (OPTIONS) and adding the necessary Access-Control-Allow-* headers to responses.

3.1 Installation

Add actix-cors to your Cargo.toml dependencies:

[dependencies]
actix-web = "4"
serde = { version = "1.0", features = ["derive"] }
# Add actix-cors
actix-cors = "0.7" # Check crates.io for latest version

Or use cargo add:

cargo add actix-cors

3.2 Basic Usage (Allowing All Origins)

The simplest setup allows requests from any origin. Use this with caution, especially in production environments handling sensitive data or requiring credentials.

// In your main.rs or where you configure your App
use actix_cors::Cors;
use actix_web::{web, App, HttpServer, HttpResponse, Responder, get};

// ... (your handler functions like `hello`) ...

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello with CORS!")
}


#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("πŸš€ Server starting at http://127.0.0.1:8080");

    HttpServer::new(|| {
        // Define permissive CORS settings
        let cors = Cors::default() // <- Construct default Cors middleware
            .allow_any_origin()    // <- Allow requests from any origin
            .allow_any_method()    // <- Allow any HTTP method
            .allow_any_header()    // <- Allow any header
            .max_age(3600);        // <- Cache preflight response for 1 hour

        App::new()
            .wrap(cors) // <- Apply CORS middleware to the App
            .service(hello)
            // ... register other services ...
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

Here, Cors::default() creates a base configuration, and methods like .allow_any_origin() configure it. The .wrap(cors) call applies the configured middleware to the entire application.

3.3 Detailed Configuration

For production scenarios, you should configure CORS more strictly:

use actix_cors::Cors;
use actix_web::{http::header, web, App, HttpServer, HttpResponse, Responder, get}; // Import header

// ... (your handler functions) ...
#[get("/")]
async fn hello() -> impl Responder { HttpResponse::Ok().body("Hello!") }

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("πŸš€ Server starting with specific CORS at http://127.0.0.1:8080");

    HttpServer::new(|| {
        let cors = Cors::default()
            // Specify allowed origins. Use origins() for multiple.
            .allowed_origin("https://your-frontend.com")
            // Or allow specific multiple origins
            // .origins(vec!["http://localhost:3000", "https://safe.domain.com"])
            .allowed_methods(vec!["GET", "POST", "PUT"]) // Specify allowed methods
            .allowed_headers(vec![ // Specify allowed headers
                header::AUTHORIZATION,
                header::ACCEPT,
                header::CONTENT_TYPE,
            ])
            .supports_credentials() // Allow cookies/auth headers (requires specific origins)
            .max_age(3600); // Cache preflight response

        App::new()
            .wrap(cors) // Apply configured CORS middleware
            .service(hello)
            // ... register other services ...
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

This configuration:

  • Allows requests only from https://your-frontend.com using .allowed_origin(). Use .origins() for a list.
  • Allows GET, POST, and PUT methods.
  • Allows specific common headers necessary for many APIs.
  • Uses .supports_credentials() to allow credentialed requests (requires specific origins, not *).

4. Applying the Middleware with .wrap()

In Actix Web, middleware is applied to an App or Scope using the .wrap() method. The Cors struct created by actix-cors implements the necessary traits to be used as middleware.

When a request comes in:

  1. The Cors middleware intercepts it.
  2. It checks if CORS handling is needed (i.e., if it's a cross-origin request).
  3. If it's a preflight (OPTIONS) request, the middleware constructs and sends the appropriate response based on your configuration, short-circuiting the request before it hits your route handlers.
  4. If it's a regular cross-origin request, the middleware lets it pass to your route handler.
  5. When your handler generates a response, the Cors middleware intercepts the outgoing response and adds the required Access-Control-Allow-* headers (like Access-Control-Allow-Origin) before sending it to the client.

Applying it via App::new().wrap(cors) ensures CORS rules are applied consistently across all defined routes within that App instance.

5. Complete Example

Here’s a combined example with a simple JSON route and specific CORS settings:

use actix_cors::Cors;
use actix_web::{http::header, web, App, HttpServer, HttpResponse, Responder, get, error};
use serde::Serialize;

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

#[get("/api/info")]
async fn get_info() -> impl Responder {
    HttpResponse::Ok().json(ApiResponse {
        message: "This API requires specific CORS headers!".to_string()
    })
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("πŸš€ Actix Server starting with specific CORS at http://127.0.0.1:8080");

    HttpServer::new(|| {
        let cors = Cors::default()
            .allowed_origin("http://localhost:5173") // Allow frontend dev server
            .allowed_origin_fn(|origin, _req_head| {
                // Example: Allow another dynamic origin based on some logic
                origin.as_bytes().ends_with(b".my-trusted-domain.com")
            })
            .allowed_methods(vec!["GET", "POST"])
            .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT, header::CONTENT_TYPE])
            .supports_credentials()
            .max_age(3600);

        App::new()
            .wrap(cors) // Apply CORS middleware
            .service(get_info)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

This example allows localhost:5173 explicitly and uses .allowed_origin_fn to demonstrate dynamically allowing origins ending with .my-trusted-domain.com.

6. Conclusion

Configuring CORS is a necessary step when building Actix Web APIs intended to be accessed by frontend applications from different origins. The actix-cors crate provides flexible middleware to handle the complexities of CORS headers and preflight requests based on your defined rules.

Always configure CORS with security in mind, restricting origins, methods, and headers as much as possible while still allowing your legitimate frontend applications to function correctly. Avoid using wildcard (*) origins in production, especially when credentials are involved.

7. Additional Resources

Related Articles

External Resources