Control de Acceso entre Orígenes (CORS)

CORS (Cross-Origin Resource Sharing) es un mecanismo que permite a los navegadores realizar solicitudes a servidores de diferentes orígenes, superando así las restricciones de la política del mismo origen.

¿Qué es la política del mismo origen?

La política del mismo origen es una función de seguridad del navegador que limita cómo un documento o script cargado desde un origen puede interactuar con recursos de otro origen. Se considera "mismo origen" cuando coinciden el protocolo, dominio y número de puerto.

¿Por qué necesitamos CORS?

Cuando una aplicación frontend necesita acceder a APIs de diferentes orígenes, se requiere soporte CORS. Por ejemplo:

  • La aplicación frontend está desplegada en https://frontend.com
  • El servicio API está desplegada en https://api.backend.com

Sin CORS, el navegador bloquearía el acceso de la aplicación frontend al servicio API.

¿Cómo funciona CORS?

CORS implementa el control de acceso entre orígenes mediante cabeceras HTTP:

  • Solicitud simple: se envía directamente, el servidor controla el acceso mediante cabeceras de respuesta
  • Solicitud de preflight: el navegador primero envía una solicitud OPTIONS para consultar si se permite el acceso, y solo envía la solicitud real si obtiene permiso

Como el navegador envía solicitudes de preflight Method::OPTIONS, es necesario añadir manejo para este tipo de solicitudes, colocando el middleware CORS en el Service.

Uso de CORS en Salvo

Salvo proporciona un middleware CORS integrado, fácil de configurar y usar. Aquí un ejemplo de código:

WARNING

Nota: El middleware .hoop(cors) se aplica al Service, no al Router. Esto permite manejar automáticamente las solicitudes OPTIONS de preflight.

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();

// Configurar enrutador backend con protección CORS
let router = Router::with_path("hello").post(hello);
let service = Service::new(router).hoop(cors);

Ejemplo de código

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>
"#;

Opciones principales de configuración

El middleware CORS ofrece varias opciones de configuración:

  • Orígenes permitidos: controla qué dominios pueden acceder al recurso
  • Métodos permitidos: especifica los métodos HTTP permitidos (GET, POST, etc.)
  • Cabeceras permitidas: especifica las cabeceras de solicitud permitidas
  • Cabeceras expuestas: especifica qué cabeceras de respuesta pueden ser accedidas por el cliente
  • Credenciales permitidas: indica si se permiten credenciales como cookies
  • Tiempo de caché para preflight: duración del caché para resultados de solicitudes de preflight

Con una configuración adecuada de CORS, se puede garantizar seguridad mientras se satisface la necesidad de acceso entre orígenes.