Adding CORS Support to Rust Rocket Applications

Last updated: April 13, 2025

1. Introduction: CORS and Rocket

When building web APIs with Rust and Rocket, you'll often need to allow frontend applications running on different domains (origins) to interact with your API. Browsers enforce the Same-Origin Policy, which prevents this by default. To enable cross-origin requests, your Rocket server needs to implement Cross-Origin Resource Sharing (CORS) by sending specific HTTP headers.

This guide explains how to add CORS support to your Rocket application, primarily using the popular rocket_cors crate. We assume you have a basic Rocket application set up, similar to the one in our Building a Simple Web API with Rust and Rocket tutorial.

2. What is CORS? (Quick Recap)

Cross-Origin Resource Sharing (CORS) is a security mechanism implemented by web browsers. It uses HTTP headers sent by the server to determine if a web page from one origin (e.g., https://my-frontend.com) is allowed to make requests to a resource at a different origin (e.g., https://api.my-backend.com).

If the server doesn't send the correct CORS headers (like Access-Control-Allow-Origin), the browser will block the frontend JavaScript code from accessing the response, resulting in a CORS error in the console. For a deeper dive, see Understanding CORS Errors and How to Fix Them.

3. Using the rocket_cors Crate

While you could manually add CORS headers to every response in Rocket, it's tedious and error-prone. The community standard is to use the rocket_cors crate, which provides a Rocket "Fairing" (middleware) to handle CORS headers automatically based on configuration.

3.1 Installation

Add rocket_cors to your Cargo.toml dependencies:

[dependencies]
rocket = { version = "0.5.0", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
# Add rocket_cors
rocket_cors = "0.6.0" # Check crates.io for the latest compatible version

Or use cargo add:

cargo add rocket_cors

3.2 Basic Usage (Allowing All Origins)

The simplest way to get started is to allow requests from any origin (*). Warning: This is often insecure for production APIs, especially those requiring authentication or handling sensitive data. Use specific origins in production whenever possible.

Modify your src/main.rs to attach the CORS fairing:

#[macro_use] extern crate rocket;

use rocket::http::Method; // Import Method
use rocket_cors::{AllowedHeaders, AllowedOrigins, CorsOptions}; // Import Cors stuff

#[get("/")]
fn index() -> &'static str {
    "Hello, CORS World!"
}

#[get("/data")]
fn get_data() -> rocket::serde::json::Json {
    rocket::serde::json::Json(serde_json::json!({ "message": "This data is CORS enabled!" }))
}


#[launch]
fn rocket() -> _ {
    println!("🚀 Rocket launching with CORS at http://127.0.0.1:8000");

    // Configure CORS
    // Try default options first, which allows GET, POST, HEAD from any origin
    // let cors = rocket_cors::CorsOptions::default().to_cors()?; // Requires handling Result

    // A more explicit way to allow everything (use with caution!)
     let cors = CorsOptions::default()
        .allowed_origins(AllowedOrigins::all()) // Allow all origins
        .allowed_methods(
            vec![Method::Get, Method::Post, Method::Patch, Method::Delete, Method::Options] // Allow common methods
                .into_iter()
                .map(From::from)
                .collect(),
        )
        .allowed_headers(AllowedHeaders::all()) // Allow all headers
        .allow_credentials(true) // Allow cookies/auth (requires specific origin, not '*') - Be careful!
        .to_cors().expect("Failed to create CORS fairing."); // Use expect for simplicity here

    rocket::build()
        .mount("/", routes![index, get_data])
        .attach(cors) // Attach the CORS fairing
}

Key changes:

  • Import types from rocket::http and rocket_cors.
  • Create CorsOptions. ::default() provides sensible defaults, but we explicitly configure it here for clarity.
  • AllowedOrigins::all() allows any origin (*).
  • .allowed_methods(...) specifies permitted HTTP methods.
  • .allowed_headers(AllowedHeaders::all()) permits any request header (needed for preflight checks with custom headers like Authorization).
  • .allow_credentials(true) is necessary if your frontend needs to send cookies or auth headers. **Note:** If you set this to true, you *cannot* use AllowedOrigins::all(); you must specify exact origins instead for security reasons. The browser will reject Access-Control-Allow-Origin: * when credentials are involved. The example above shows true for demonstration, but it would technically require a specific origin instead of all() to work correctly with credentials in a browser.
  • .to_cors().expect(...) converts the options into a Fairing. We use .expect() for simplicity; proper error handling would use ? or match in a real application.
  • .attach(cors) adds the CORS fairing to the Rocket instance.

3.3 Detailed Configuration

For production, configure CORS more strictly:

use rocket::http::{Method, Header};
use rocket_cors::{AllowedHeaders, AllowedOrigins, CorsOptions};
use std::str::FromStr;

// ... routes ...

#[launch]
fn rocket() -> _ {
     println!("🚀 Rocket launching with specific CORS at http://127.0.0.1:8000");

     // Define allowed origins
    let allowed_origins = AllowedOrigins::some_exact(&[
        "https://your-frontend-app.com",
        "http://localhost:3000", // For local development
    ]);

    let cors = CorsOptions {
        allowed_origins,
        allowed_methods: vec![Method::Get, Method::Post].into_iter().map(From::from).collect(),
        allowed_headers: AllowedHeaders::some(&[
            "Authorization",
            "Accept",
            "Content-Type", // Allow common headers
        ]),
        allow_credentials: true, // Allow cookies/auth headers
        // max_age: Some(3600), // Optional: Cache preflight response for 1 hour
        ..Default::default() // Use defaults for other options
    }
    .to_cors().expect("Failed to create CORS fairing.");


    rocket::build()
        .mount("/", routes![index, get_data]) // Add your actual routes
        .attach(cors)
}

This configuration:

  • Allows requests only from https://your-frontend-app.com and http://localhost:3000.
  • Allows only GET and POST methods.
  • Allows specific headers like Authorization and Content-Type.
  • Explicitly allows credentials (remember, this requires specific origins, not *).

4. Understanding Rocket Fairings

Fairings are Rocket's mechanism for intercepting and modifying requests and responses. They act like middleware. The rocket_cors crate provides a pre-built fairing that automatically:

  1. Intercepts incoming requests.
  2. Checks if it's a CORS preflight request (OPTIONS method with specific headers).
  3. If preflight, responds appropriately based on the CorsOptions configuration.
  4. If a regular request, allows it to proceed to the route handler.
  5. Intercepts the outgoing response from the handler.
  6. Adds the necessary Access-Control-Allow-* headers to the response based on the configuration and the request's Origin header.

By attaching the CORS fairing with .attach(cors), you enable this behavior for all routes mounted on that Rocket instance.

5. Complete Example

Here's a consolidated example combining a simple route and specific CORS configuration:

#[macro_use] extern crate rocket;

use rocket::http::Method;
use rocket_cors::{AllowedHeaders, AllowedOrigins, CorsOptions};
use rocket::serde::json::Json;
use serde::Serialize;

#[derive(Serialize)]
struct Message {
    content: String,
}

#[get("/")]
fn index() -> &'static str {
    "API Root"
}

#[get("/hello")]
fn hello() -> Json {
    Json(Message { content: "Hello from Rocket with CORS!".to_string() })
}

#[launch]
fn rocket() -> _ {
    println!("🚀 Rocket launching with specific CORS at http://127.0.0.1:8000");

    let allowed_origins = AllowedOrigins::some_exact(&[
        "http://localhost:5173", // Example: A Vite frontend dev server
        "https://my-production-frontend.com",
    ]);

    let cors = CorsOptions {
        allowed_origins,
        allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(), // Only allow GET
        allowed_headers: AllowedHeaders::none(), // No custom headers needed for simple GET
        allow_credentials: false, // No credentials needed for this example
        ..Default::default()
    }
    .to_cors().expect("Failed to create CORS fairing.");

    rocket::build()
        .mount("/", routes![index, hello])
        .attach(cors)
}

6. Conclusion

Handling CORS in Rocket applications is essential for enabling cross-origin communication with frontend applications. The rocket_cors crate provides a convenient and configurable fairing to manage the required HTTP headers automatically.

Remember to configure CORS securely, especially in production, by specifying allowed origins, methods, and headers precisely rather than using permissive wildcards, particularly when dealing with credentials or sensitive data.

7. Additional Resources

Related Articles

External Resources