Cross-Origin Resource Sharing

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

What is the Same-Origin Policy?

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

Why is CORS Needed?

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

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

Without CORS, the browser would block 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 access through response headers
  • Preflight requests: The browser first sends an OPTIONS request to ask if cross-origin access is allowed, and only sends the actual request after receiving permission

Since browsers send preflight requests using Method::OPTIONS, you need to add handling for these requests by adding the CORS middleware to the Service.

Using CORS in Salvo

Salvo provides built-in CORS middleware that can be easily configured and used. Here's an example:

WARNING

Note that the .hoop(cors) middleware is applied to the Service, not to the Router. Note that the .hoop(cors) middleware is applied to the Service, not to the Router. Note that the .hoop(cors) middleware is applied to the Service, not to the Router. This automatically handles OPTIONS preflight requests.

let cors = Cors::new()
    .allow_origin(["http://127.0.0.1:5800", "http://localhost:5800"])
    .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
cors/src/main.rs
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:5800
// - Allow specific HTTP methods
// - Allow authorization header
let cors = Cors::new()
    .allow_origin(["http://127.0.0.1:5800", "http://localhost:5800"])
    .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 5800
let acceptor = TcpListener::new("0.0.0.0:5800").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 which HTTP methods are allowed (GET, POST, etc.)
  • Allowed headers: Specifies which request headers are allowed
  • Exposed headers: Specifies which response headers can be accessed by the client
  • Allow credentials: Whether to allow requests to include credential information like cookies
  • Preflight cache duration: How long preflight request results are cached

By properly configuring CORS, you can ensure both security and meet cross-origin access requirements.