Last updated: April 13, 2025
Table of Contents
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
, andPUT
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:
- The
Cors
middleware intercepts it. - It checks if CORS handling is needed (i.e., if it's a cross-origin request).
- 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. - If it's a regular cross-origin request, the middleware lets it pass to your route handler.
- When your handler generates a response, the
Cors
middleware intercepts the outgoing response and adds the requiredAccess-Control-Allow-*
headers (likeAccess-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
- Getting Started with Rust
- Rust Ownership and Borrowing Explained
- Understanding CORS Errors and How to Fix Them
- Building a Simple Web API with Rust and Actix Web
- Common Rust Crates for Web Development