Qué es CORS y cómo funciona en el navegador

Autor: Publicado

En pocas palabras: CORS (Cross-Origin Resource Sharing) es un mecanismo que permite al servidor autorizar al navegador a compartir una respuesta con un script que se ejecuta en otro origen. Las cabeceras de respuesta del servidor establecen los permisos y el navegador se encarga de aplicarlos. Por tanto, un error de CORS suele indicar un problema de permisos para leer la respuesta, no necesariamente una falta de conexión con el servidor.

Por qué el navegador limita el acceso entre orígenes

Los navegadores aplican la política del mismo origen, o same-origin policy. Esta limita la capacidad de un script para leer datos de otro origen. Así, una página abierta en una pestaña no puede obtener y leer libremente datos privados de otros servicios en los que el usuario haya iniciado sesión.

El origen (origin) es la combinación de tres elementos: protocolo, nombre de host y puerto. Basta con que cambie uno de ellos para que el origen sea distinto. La ruta que aparece después del nombre de host no influye en esta comparación.

Para la página https://tienda.example, la comparación es la siguiente:

Dirección de destino¿Mismo origen?Motivo
https://tienda.example/productosSíSolo cambia la ruta
https://api.tienda.exampleNoEl nombre de host es distinto
http://tienda.exampleNoEl protocolo es distinto
https://sklep.example:8443NoEl puerto es distinto del predeterminado, 443

CORS permite al servidor flexibilizar esta política de forma controlada. No es un método HTTP, como GET o POST, sino un mecanismo basado en cabeceras HTTP.

Un ejemplo sencillo: una tienda obtiene una lista de productos

Supongamos que la página está en https://tienda.example y que la API, es decir, la interfaz que proporciona los datos de la aplicación, está en https://api.tienda.example. El script de la página quiere obtener los productos:

fetch('https://api.sklep.example/produkty')
  .then(respuesta => respuesta.json())
  .then(productos => console.log(productos));

Como las direcciones tienen orígenes distintos, el navegador añade a la petición una cabecera que indica el origen de la página:

Origin: https://sklep.example

El servidor de la API puede responder con los datos y una cabecera que autorice el acceso:

Access-Control-Allow-Origin: https://sklep.example

El navegador compara ese valor con el origen de la página. Si la autorización coincide, permite que el script acceda a la respuesta. Si falta la cabecera o indica otra dirección, el script no puede leer la respuesta, aunque el servidor haya devuelto el estado 200 OK.

La cabecera de autorización debe proceder del servidor de la API. Añadir Access-Control-Allow-Origin al código de la petición en el navegador no resuelve el problema.

Cuándo envía el navegador primero una petición OPTIONS

No todas las peticiones entre orígenes distintos se envían directamente. En algunos casos, el navegador realiza primero una preflight, una petición preliminar que comprueba si el servidor autoriza el acceso.

Por ejemplo, enviar un pedido con el método POST y el tipo de contenido application/json activa este paso previo. Lo mismo ocurre al utilizar la cabecera Authorization. La petición preliminar puede tener este aspecto:

OPTIONS /pedidos HTTP/1.1
Origin: https://sklep.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

El servidor debe responder con una autorización que incluya el origen, el método y las cabeceras necesarias:

Access-Control-Allow-Origin: https://sklep.example
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type

Solo cuando la comprobación se completa correctamente, el navegador envía la petición POST propiamente dicha. La respuesta a esa petición POST también debe incluir la cabecera Access-Control-Allow-Origin adecuada.

No todas las peticiones POST requieren una preflight. También influyen las cabeceras y el tipo de contenido enviado; una petición GET normal, sin cabeceras adicionales, a menudo no necesita una petición preliminar.

Cookies y asterisco: dos errores importantes que evitar

Una API pública cuyas respuestas deban ser accesibles para scripts de cualquier origen puede devolver:

Access-Control-Allow-Origin: *

Sin embargo, el asterisco no es una solución válida para las peticiones que envían cookies en un modo que incluye credenciales. En ese caso, el código puede usar credentials: 'include', y el servidor debe indicar un origen permitido concreto y devolver:

Access-Control-Allow-Origin: https://sklep.example
Access-Control-Allow-Credentials: true

La configuración de CORS por sí sola no garantiza el envío de cookies. También se aplican las reglas de las cookies, entre ellas SameSite, y las restricciones de privacidad del navegador.

Si el servidor admite varios orígenes de confianza, debe comprobar la cabecera Origin con una lista de valores permitidos. Devolver sin comprobarlo cualquier origen recibido y autorizar el uso de credenciales puede exponer respuestas privadas. Cuando la respuesta depende de Origin, también hay que incluir Vary: Origin para que la caché distinga las distintas variantes de la respuesta.

Cómo diagnosticar un error de CORS

Abre la pestaña de red de las herramientas de desarrollo del navegador y realiza estas cuatro comprobaciones:

  1. Compara el protocolo, el host y el puerto de la página con los de la API.
  2. Comprueba si hay una petición OPTIONS y si el servidor responde correctamente a ella.
  3. Revisa las cabeceras de respuesta de la petición propiamente dicha, también cuando el servidor devuelve un error.
  4. Comprueba que la autorización incluya el método, las cabeceras y, si corresponde, las credenciales utilizadas.

Que la llamada funcione en curl no demuestra que CORS esté bien configurado: esta herramienta no aplica las restricciones del navegador. Usar mode: 'no-cors' tampoco permite leer el contenido JSON: produce una respuesta opaca cuyo contenido no es accesible para el script.

CORS no sustituye al inicio de sesión, al control de permisos ni a la protección contra CSRF, que consiste en provocar acciones no deseadas desde otra página. Algunas peticiones pueden llegar al servidor aunque se bloquee la lectura de la respuesta. Para resolver el problema, empieza por la configuración de la API o de un intermediario bajo tu control, no por desactivar las protecciones del navegador.

Consulta también: ¿Qué es una API?

Lee también

Descubre cómo PHP-FPM atiende peticiones PHP, en qué se diferencia de un servidor web y cómo ajustar sus procesos al tráfico y los recursos.
Conoce BIND, el software abierto de servidor DNS: sus funciones, cómo gestiona dominios y cuándo conviene instalarlo por tu cuenta.
Descubre qué son el registro REGON de Polonia y su número, en qué se diferencian de NIP y KRS y cómo verificar los datos de una empresa.