Cross-Origin Control

CORS (Cross-Origin Resource Sharing) is a mechanism that allows browsers to make requests to cross-origin servers, thereby overcoming the restrictions imposed by the browser's same-origin policy.

What is the Same-Origin Policy?

The same-origin policy is a security feature of browsers that restricts how documents or scripts loaded from one origin can interact with resources from another origin. "Same origin" refers to having the same protocol, domain, and port number.

Why is CORS Needed?

CORS support is required when a frontend application needs to access APIs from a different origin. For example:

  • The frontend application is deployed at https://frontend.com
  • The API service is deployed at https://api.backend.com

Without CORS, the browser will prevent the frontend application from accessing the API service.

How CORS Works

CORS implements cross-origin access control through a series of HTTP headers:

  • Simple Requests: Sent directly, with the server controlling whether access is allowed via response headers.
  • Preflight Requests: The browser first sends an OPTIONS request to ask the server if cross-origin access is allowed, and only sends the actual request after receiving permission.

Since the browser sends preflight requests with Method::OPTIONS, it is necessary to handle such requests. The CORS middleware must be added to the Service.

Using CORS in Salvo

Salvo provides built-in CORS middleware that can be easily configured and used. Here is an example code snippet:

Warning

Note: The .hoop(cors) middleware is applied to the Service, not the Router. This ensures automatic handling of OPTIONS preflight requests.

let cors = Cors::new()
    .allow_origin(["http://127.0.0.1:8698", "http://localhost:8698"])
    .allow_methods(vec![Method::GET, Method::POST, Method::DELETE])
    .allow_headers("authorization")
    .into_handler();

// Set up backend router with CORS protection
let router = Router::with_path("hello").post(hello);
let service = Service::new(router).hoop(cors);

Example Code

main.rs
Cargo.toml
use salvo::cors::Cors;
use salvo::http::Method;
use salvo::prelude::*;

#[tokio::main]
async fn main() {
    // Initialize logging system
    tracing_subscriber::fmt().init();
    // Start both backend and frontend servers concurrently
    tokio::join!(backend_server(), frontend_server());
}

async fn backend_server() {
    // Handler that returns a simple message for CORS demonstration
    #[handler]
    async fn hello() -> &'static str {
        "hello, I am content from remote server."
    }

    // Configure CORS middleware with specific settings:
    // - Allow requests from localhost:8698
    // - Allow specific HTTP methods
    // - Allow authorization header
    let cors = Cors::new()
        .allow_origin(["http://127.0.0.1:8698", "http://localhost:8698"])
        .allow_methods(vec![Method::GET, Method::POST, Method::DELETE])
        .allow_headers("authorization")
        .into_handler();

    // Set up backend router with CORS protection
    let router = Router::with_path("hello").post(hello);
    let service = Service::new(router).hoop(cors);

    // Start backend server on port 5600
    let acceptor = TcpListener::new("0.0.0.0:5600").bind().await;
    Server::new(acceptor).serve(service).await;
}

async fn frontend_server() {
    // Handler that serves the HTML page with CORS test
    #[handler]
    async fn index() -> Text<&'static str> {
        Text::Html(HTML_DATA)
    }

    // Set up frontend router to serve the test page
    let router = Router::new().get(index);
    // Start frontend server on port 8698
    let acceptor = TcpListener::new("0.0.0.0:8698").bind().await;
    Server::new(acceptor).serve(router).await;
}

// HTML template with JavaScript code to test CORS
// Contains a button that triggers a POST request to the backend server
const HTML_DATA: &str = r#"
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Salvo Cors</title>
</head>
<body>
<button id="btn">Load Content</button>
<div id="content"></div>
<script>
document.getElementById("btn").addEventListener("click", function() {
    fetch("http://127.0.0.1:5600/hello", {method: "POST", headers: {authorization: "abcdef"}}).then(function(response) {
        return response.text();
    }).then(function(data) {
        document.getElementById("content").innerHTML = data;
    });
});
</script>
</body>
</html>
"#;

Main Configuration Options

The CORS middleware provides various configuration options:

  • Allowed Origins: Controls which domains can access resources.
  • Allowed Methods: Specifies allowed HTTP methods (GET, POST, etc.).
  • Allowed Headers: Specifies allowed request headers.
  • Exposed Headers: Specifies which response headers can be accessed by the client.
  • Allow Credentials: Determines whether requests can include credentials such as cookies.
  • Preflight Request Cache Time: The cache duration for preflight request results.

By configuring CORS appropriately, you can ensure both security and the ability to meet cross-origin access requirements.