El clásico error de CORS (Cross-Origin Resource Sharing)

  INTRODUCCIÓN 

Si eres desarrollador, ya sea fullstack o especializado en backend o frontend; seguro en algún momento te habrás topado con el clásico error de CORS. Este problema aparece cuando una aplicación intenta acceder a recursos de un dominio diferente al suyo, y aunque a primera vista puede parecer un obstáculo frustrante, en realidad es una señal de las medidas de seguridad que los navegadores implementan para proteger tanto tu aplicación como a los usuarios. En esta sección, exploraremos en profundidad qué es CORS, por qué surge este error y cómo puedes abordarlo para mejorar la comunicación segura entre distintos orígenes.

1.- Que son los CORS?

Los CORS (del ingles, Cross-Origin Resource Sharing, que en español significa intercambio de recursos de origen cruzado) son un mecanismo de seguridad que aplican los navegadores web cuando estamos haciendo una petición a un recurso que está alojado en otro origen. Si es así, el navegador automáticamente comprobará las cabeceras HTTP buscando una autorización expresa por parte del servidor.

2.- Ejemplo de errores CORS?

Los escenarios donde se presentan estos clásicos errores, son mayormente cuando por ejemplo:

- Estás diseñando una aplicación que está ubicada en miwebapp.com y las peticiones de datos las realiza a una API externa, situada en api.miwebapp.pe. En cuanto hagas la consulta con el método fetch de JavaScript(JS) al dominio (o subdominio donde tienes la API) te aparecerá su conocido error:

- Otro ejemplo es cuando intentes cargar Web Fonts externos(usando @font-face), vídeos, texturas, etc.

- Si no existiese CORS, pasaría lo siguiente; imagina que estás navegando por Internet y entras en un sitio legítimo cualquiera como LinkedIn. Ahí ya tienes una sesión iniciada, de repente haces clic en un post que te lleva a una página maliciosa como ganadinero.com:
ganadinero.com descarga y ejecuta en tu navegador un código JavaScript malicioso que emite una petición a LinkedIn (como por ejemplo alguna petición que fuerce devolver tus token, tus credenciales, tu email, etc.). LinkedIn recibe la petición de ganadinero.com y al no existir CORS, nadie se encarga de validar si ese origen es legítimo y puede realizar peticiones. Así que el sitio real devuelve tus datos al script de ganadinero.com (porque la única forma de comprobar que eres tú es que haya iniciado sesión, y tú lo has hecho a través del navegador). La página falsa recibe tus datos y aquí comienza el problema: listas de SPAM, estafas bancarias y demás problemas de seguridad que se ven hoy en día.

CORS Errors
3.- Cabeceras relacionadas a los CORS?

Access-Control-Allow-Origin
Esta cabecera de respuesta indica cuáles orígenes tienen permitido acceder a los recursos del servidor. Cuando un navegador realiza una solicitud de origen cruzado, examina esta cabecera en la respuesta del servidor para determinar si la solicitud debe ser permitida o bloqueada. Su objetivo principal es: Especificar qué dominios, esquemas o puertos pueden realizar solicitudes a un recurso y prevenir que sitios web maliciosos realicen solicitudes no autorizadas a tu API o servidor en nombre de un usuario.
Como funciona: El navegador realiza una solicitud a un dominio diferente al que sirve la página actual, por ejemplo; desde https://miwebapp.com a https://api.sunat.gob.pe.
El Asterisco o comodín (*):
Permite el acceso desde cualquier origen. Esto significa que cualquier sitio web puede realizar solicitudes a tu recurso.
Generalmente se usa para recursos públicos que no manejan información sensible y que se espera sean accedidos desde cualquier lugar.
Usar * puede ser inseguro si el recurso requiere autenticación (por ejemplo, mediante cookies o tokens de sesión), ya que no se puede combinar con Access-Control-Allow-Credentials: true (a menos que se especifique un origen explícito).
 La etiqueta <origin>:
Permite el acceso únicamente desde el origen especificado. El valor debe coincidir exactamente con el valor de la cabecera Origin enviada por el navegador en la solicitud.
Ejemplo de uso: Access-Control-Allow-Origin: https://miwebapp.com
Es la opción más segura y recomendada cuando conoces los orígenes que deben tener acceso a tus recursos.

Access-Control-Allow-Methods
Esta cabecera de respuesta indica qué métodos(verbos) están permitidos. Por ejemplo, podemos indicar que solo se acepten GET y POST a un recurso en concreto (pero nunca PUT o PATCH). Es especialmente crucial cuando el navegador realiza una solicitud de comprobación previa (preflight request). Su propósito es informar al navegador qué métodos HTTP (como GET, POST, PUT, DELETE, etc.) son aceptados por el servidor para el recurso solicitado desde un origen cruzado, de lo contrario la llamada será bloqueada.
Métodos HTTP Comunes que se pueden listar:
  • GET: Solicita una representación de un recurso específico.
  • POST: Envía datos a un recurso para ser procesados (por ejemplo, crear una nueva entidad).
  • PUT: Reemplaza completamente un recurso existente con los datos proporcionados.
  • DELETE: Elimina un recurso específico.
  • PATCH: Aplica modificaciones parciales a un recurso.
  • OPTIONS: Describe las opciones de comunicación para el recurso de destino (usado para las preflight requests).
  • HEAD: Solicita las cabeceras que se devolverían si se hiciera una solicitud GET.

Access-Control-Allow-Headers
Esta cabecera de respuesta es utilizada por el servidor para indicar cuáles cabeceras HTTP pueden ser usadas durante la solicitud real, después de una solicitud de comprobación previa (preflight request) exitosa. Su objetivo principal es, especificar las cabeceras HTTP que el navegador está autorizado a enviar al servidor en una solicitud de origen cruzado. Esencial cuando la aplicación cliente necesita enviar cabeceras personalizadas como (X-API-Key, X-Auth-Token) o ciertas cabeceras estándar que no son consideradas "simples" como (Content-Type con un valor diferente a application/x-www-form-urlencoded, multipart/form-data, o text/plain). Si las cabeceras que hemos adjuntado en la petición no están presentes en Access-Control-Allow-Headers, la llamada será cancelada y no podrá ser procesada.
Sintaxis: Access-Control-Allow-Headers: <header-name>[, <header-name>]*

Accept: Informa al servidor sobre los tipos de contenido (MIME types) que el cliente es capaz de entender y procesar en la respuesta.
Ejemplo: Accept: application/json, text/html, application/xml; q=0.9, image/webp; q=0.8
Esto le dice al servidor que el cliente prefiere JSON, pero también acepta HTML, XML (con menor preferencia) e imágenes WebP (con aún menor preferencia). El parámetro q indica el factor de calidad relativo.

Accept-Language: Indica al servidor los idiomas humanos que el cliente prefiere para la respuesta.
Ejemplo: Accept-Language: es-PE, es;q=0.9, en;q=0.8
Esto significa que el cliente prefiere español de Perú (es-PE), luego cualquier variante de español (es) con una preferencia ligeramente menor, y finalmente inglés (en) con una preferencia aún menor.

Content-Language: Especifica el o los idiomas del contenido que se está enviando en el cuerpo de la solicitud (o que se enviará en el cuerpo de la respuesta).
Ejemplo (en una solicitud): Content-Language: es-ES (para datos en español de España).

Content-Type: Describe el tipo de medio (MIME type) del cuerpo de la solicitud que se está enviando al servidor. Para que la cabecera Content-Type sea considerada "simple" en el contexto de CORS (y no active una preflight request), su valor debe ser uno de los siguientes:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded

Save-Data: (Client Hint) Indica una preferencia del usuario para reducir el uso de datos.
Ejemplo: Save-Data: on
Cuando esta cabecera está presente y activada, el servidor puede optar por enviar respuestas más ligeras (imágenes comprimidas, menos funcionalidades, etc.) para ayudar al usuario a ahorrar datos móviles o reducir los tiempos de carga en conexiones lentas.

Access-Control-Expose-Headers
Componente vital de CORS que cierra la brecha entre la política de seguridad del mismo origen y la necesidad de las aplicaciones web modernas de acceder a información de cabecera más allá del conjunto predeterminado. Al permitir que el servidor especifique qué cabeceras personalizadas pueden ser vistas por el cliente, facilita una comunicación más rica y flexible en entornos de origen cruzado.
Si el servidor desea que el cliente tenga acceso a otras cabeceras de respuesta (cabeceras que no están en la lista segura predeterminada), debe especificar esas cabeceras en la cabecera Access-Control-Expose-Headers en la respuesta de la solicitud de CORS. Sin esto, cualquier cabecera personalizada o no estándar que envíe el servidor en su respuesta sería invisible para el JavaScript del cliente. Esto limitaría severamente la capacidad de las aplicaciones web de comunicar información adicional a través de cabeceras en solicitudes de origen cruzado.
Sintaxis: Access-Control-Expose-Headers: <nombre-cabecera-1>, <nombre-cabecera-2>, ..., <nombre-cabecera-N>
Ejemplo: Access-Control-Expose-Headers: X-My-Custom-Header, X-API-Version

Access-Control-Allow-Credentials: Esta cabecera es una respuesta HTTP crucial que indica al navegador si la respuesta a una solicitud de origen cruzado (CORS) puede ser expuesta al código JavaScript del frontend cuando la solicitud incluye credenciales. Por defecto, los navegadores no envían credenciales en las solicitudes CORS por razones de seguridad. Esto es para mitigar riesgos como el CSRF (Cross-Site Request Forgery).

En el contexto de CORS, las credenciales se refieren a:
Cookies: Especialmente las cookies de sesión que se usan para mantener el estado de la sesión de un usuario.
Cabeceras de autorización: Como la cabecera Authorization que contiene tokens (por ejemplo, Bearer tokens o Basic Auth).
El único valor válido para esta cabecera es true (en minúsculas). Si no necesitas que las credenciales se incluyan en las solicitudes de origen cruzado, simplemente omitir la cabecera (no establecer en false, ya que este no es un valor válido).

Para evitar errores, se deben de configurar correctamente en el servidor, por ejemplo:
Lo correcto:
Access-Control-Allow-Origin: https://miwebapp.com
Access-Control-Allow-Credentials: true
Lo incorrecto:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

4.- El proceso de Preflight?

Este proceso es un mecanismo de seguridad implementado por los navegadores web como parte del estándar CORS. Antes de enviar ciertas solicitudes HTTP "reales" de origen cruzado, el navegador realiza una solicitud previa (preflight request) al servidor para determinar si la solicitud real es segura de enviar.

La razón principal es la seguridad. Algunas operaciones HTTP son consideradas "peligrosas" o "no seguras" si se realizan en un contexto de origen cruzado sin una verificación previa. Por ejemplo, una solicitud DELETE podría eliminar datos, o una solicitud POST con un Content-Type inusual podría ejecutar un código no esperado en el servidor.

Este proceso es una pieza fundamental de la seguridad de CORS, asegura que las interacciones de origen cruzado peligrosas sean negociadas y autorizadas explícitamente por el servidor antes de que puedan tener un impacto real.

¿Cuándo se activa un Preflight?
Una solicitud de Preflight se activa automáticamente por el navegador (no por el código JavaScript del desarrollador) cuando una solicitud de origen cruzado no cumple con los criterios de ser una "solicitud simple".







Comentarios

Entradas populares de este blog

Arquitectura de microservicios: Una guía desde mi experiencia como desarrollador fullstack

API REST FULL: La guía completa sobre los status code