DEV Community

Juan Carlos Garcia Esquivel
Juan Carlos Garcia Esquivel

Posted on

¿Cómo funciona CORS y por qué protege la seguridad en peticiones HTTP?

cors-negotiation-cover-16-9.png

CORS (Cross-Origin Resource Sharing) es un mecanismo de seguridad basado en cabeceras HTTP que permite a un servidor indicar cualquier origen (dominio, esquema o puerto) diferente al suyo desde el cual un navegador tiene permiso para cargar recursos. CORS es una flexibilización controlada de la Same-Origin Policy (SOP), diseñada para prevenir ataques maliciosos como el robo de datos sensibles a través de scripts cargados en el cliente, gestionando de forma segura solicitudes entre diferentes dominios.

Tabla de contenidos

En el diseño de aplicaciones web modernas y el desarrollo de APIs bajo el estándar REST, los navegadores web aplican restricciones estrictas para proteger la privacidad y seguridad de los usuarios. Históricamente, la política del mismo origen impedía que un script en un sitio web realizara peticiones de red hacia un dominio diferente. CORS nace para resolver esta limitación de forma estructurada, permitiendo transferencias de datos entre dominios cruzados mediante un protocolo de negociación basado en cabeceras de ida y vuelta.

¿Qué es CORS?

Imagina que quieres obtener un paquete de la casa de tu vecino (el Servidor/API) y mandas a un cartero (el Navegador) a recogerlo en tu nombre (el Frontend). Tu vecino le entrega el paquete al cartero sin problemas, pero le pega una nota (las cabeceras HTTP) que dice: "Este paquete solo puede ser entregado a personas que vivan en mi misma calle". Cuando el cartero regresa a tu casa y nota que tú no vives en la misma calle del vecino, destruye el paquete antes de dejártelo leer.

CORS (Cross-Origin Resource Sharing) es precisamente ese mecanismo de control. Permite que servidores web externos autoricen explícitamente (mediante notas adhesivas o cabeceras de respuesta) a ciertos orígenes (sitios web) para consumir sus recursos, indicándole al cartero (el navegador) cuándo es seguro entregarte la información.

Conceptos Clave

  • Origin: La combinación única de un protocolo (ej. https), un host/dominio (ej. api.ejemplo.com) y un puerto (ej. :443 o :8080).
  • Same-Origin Policy (SOP): Regla de seguridad del navegador que impide que un script de un sitio acceda a datos de otro sitio con diferente origen.
  • Cross-Origin Request: Petición HTTP realizada desde un recurso con un origen hacia otro servidor con origen distinto.
  • Preflight Request: Petición de verificación preliminar con el método OPTIONS que el navegador realiza antes de enviar la petición real si esta última es compleja.
  • Wildcard (*): Valor especial en las cabeceras de CORS que autoriza el acceso a cualquier origen de forma pública.
  • Credentials: Información de autenticación que acompaña a una petición, como cookies, tokens de autorización en cabeceras (Authorization) o certificados SSL de cliente.

La base: Same-Origin Policy (SOP)

Antes de entender CORS, es fundamental comprender la Same-Origin Policy (SOP). Esta es una medida de seguridad crítica implementada por los navegadores que dicta que un documento o script cargado por un origen solo puede interactuar con recursos de otro origen si ambos comparten el mismo dominio, protocolo y puerto.

Si un usuario visita https://banco.com y simultáneamente tiene abierta una pestaña con https://sitio-malicioso.com, la SOP evita que los scripts de sitio-malicioso.com realicen peticiones asíncronas (como fetch o XMLHttpRequest) a banco.com usando la sesión activa del usuario para extraer información privada.

Un origen se considera idéntico solo si los siguientes tres elementos coinciden exactamente:

  1. Protocolo (ej. http vs. https).
  2. Host/Dominio (ej. api.ejemplo.com vs. ejemplo.com).
  3. Puerto (ej. localhost:3000 vs. localhost:8080).

Mecanismo de negociación de CORS

Cuando una aplicación cliente realiza una solicitud de origen cruzado, el navegador intercepta la petición y añade automáticamente una cabecera llamada Origin indicando de dónde proviene la solicitud (ej. Origin: https://mi-app.com).

El servidor que recibe la solicitud debe responder con cabeceras de control de acceso específicas. Si el origen cliente está autorizado, el servidor responde con la cabecera Access-Control-Allow-Origin. El navegador inspecciona esta cabecera:

  • Si coincide con el origen de la aplicación o contiene un wildcard (*), permite al cliente acceder a la respuesta del servidor.
  • Si no coincide o la cabecera no está presente, el navegador bloquea la respuesta y lanza un error de consola de tipo CORS.

El rol del navegador vs. el servidor (¿Quién hace el bloqueo?)

Es muy común confundir CORS con una medida de seguridad para proteger al servidor de peticiones externas. En realidad, funciona de la siguiente manera:

  • El servidor procesa todo: Cuando una petición cruza de origen, el servidor del API la recibe, la procesa, interactúa con la base de datos y responde normalmente (ej. 200 OK). El servidor no aborta la ejecución de la lógica del negocio a menos que implemente validaciones de seguridad adicionales en su código.
  • El navegador bloquea la lectura: El navegador del usuario final es el que intercepta la respuesta. Al notar la ausencia de la cabecera Access-Control-Allow-Origin autorizando el origen web de origen, el navegador oculta la respuesta al código JavaScript, destruyéndola y lanzando el error de CORS.

¿Cómo sabe el API quién le escribe?

El navegador web adjunta de forma automática e inalterable la cabecera Origin en cada petición cruzada (ej. Origin: https://mi-frontend.com). El backend lee esta cabecera de la solicitud HTTP entrante y decide si debe incluir Access-Control-Allow-Origin: https://mi-frontend.com en su respuesta.

El caso de las APIs Públicas y el Wildcard (*)

Si estás construyendo un API de libre acceso (como una API del clima o la base de datos de Pokémon), te interesa que cualquier frontend en el mundo pueda consumir tus datos directamente desde el navegador de los usuarios. En este caso, el servidor responde con la cabecera:

Access-Control-Allow-Origin: *
Enter fullscreen mode Exit fullscreen mode

El comodín * le indica al navegador: "este recurso es de acceso público y cualquier origen tiene permiso de leer la respuesta".

[!important] CORS no protege tu API contra atacantes directos
CORS es un protocolo de seguridad diseñado exclusivamente para proteger al usuario final dentro del navegador (evitando que sitios maliciosos hagan peticiones silenciosas usando su sesión en pestañas secundarias).

  • Herramientas como curl, Postman, Python o cualquier cliente HTTP de backend no son navegadores y, por ende, ignoran las reglas de CORS. Realizarán la petición y leerán la respuesta de tu servidor sin ningún tipo de restricción.
  • Conclusión: Para proteger y restringir el acceso real a los recursos de tu API, debes implementar mecanismos de autenticación y autorización como API Keys, JWT (JSON Web Tokens) o OAuth, en lugar de depender únicamente de CORS.

Tipos de peticiones bajo CORS

El navegador clasifica las solicitudes en dos categorías para decidir si debe realizar una verificación previa de seguridad:

Peticiones simples (Simple Requests)

Son aquellas solicitudes que no desencadenan una verificación previa. Deben cumplir con las siguientes restricciones:

  • Métodos permitidos: GET, POST o HEAD.
  • Cabeceras manuales permitidas: solo cabeceras estándar como Accept, Accept-Language, Content-Language o Content-Type.
  • Para Content-Type, los únicos valores permitidos son: application/x-www-form-urlencoded, multipart/form-data o text/plain.

Peticiones con verificación previa (Preflight Requests)

Si la petición no cumple con las condiciones de una petición simple (por ejemplo, si usa el método PUT/DELETE, o envía un Content-Type como application/json, o incluye cabeceras personalizadas como Authorization), el navegador realiza automáticamente una petición previa de tipo Preflight.

  1. El navegador envía primero una petición HTTP usando el método OPTIONS al servidor destino. Esta petición incluye cabeceras de consulta:
    • Access-Control-Request-Method: El método real que se quiere usar (ej. PUT).
    • Access-Control-Request-Headers: Las cabeceras personalizadas que se quieren enviar (ej. Authorization).
  2. El servidor debe responder autorizando estas condiciones usando cabeceras de respuesta (Access-Control-Allow-Methods y Access-Control-Allow-Headers).
  3. Si la respuesta OPTIONS es aprobada por el navegador, este procede a enviar la petición real original.

Diagramas de Secuencia: Flujos de CORS

A. Petición Simple (CORS Exitoso vs. Bloqueado)

En peticiones simples (GET/POST sin cabeceras personalizadas), la solicitud viaja directamente y el navegador valida el origen tras recibir la respuesta.

Flujos de CORS|800

B. Petición con Preflight (OPTIONS)

En peticiones complejas (ej. métodos PUT/DELETE o envío de JSON), el navegador realiza primero una petición de verificación OPTIONS. Si el servidor la rechaza, la petición real nunca llega a enviarse.

Mermaid Diagram|798


Cabeceras CORS esenciales

Para configurar correctamente la comunicación de origen cruzado en servidores, se utilizan las siguientes cabeceras de respuesta HTTP:

  • Access-Control-Allow-Origin: Especifica qué orígenes pueden acceder al recurso. Puede ser un origen explícito (ej. https://mi-app.com) o un wildcard (*).
  • Access-Control-Allow-Methods: Lista los métodos HTTP permitidos en peticiones cruzadas (ej. GET, POST, PUT, DELETE, OPTIONS).
  • Access-Control-Allow-Headers: Lista las cabeceras personalizadas que el cliente puede enviar en la petición real.
  • Access-Control-Allow-Credentials: Indica si la respuesta a la petición se puede exponer cuando la petición se realiza con credenciales (como cookies o cabeceras de autorización HTTP). Si se establece en true, Access-Control-Allow-Origin no puede ser * y debe ser un origen específico.

Cómo se usa: Implementación en Node.js

Para corregir los errores de CORS, la configuración debe realizarse en el lado del servidor. La forma estándar y recomendada de habilitarlo en un backend Node.js con Express es utilizando el paquete oficial cors:

npm install cors
Enter fullscreen mode Exit fullscreen mode

1. Configuración Estática Básica

Si tienes un conjunto estático de dominios permitidos:

const express = require('express');
const cors = require('cors');
const app = express();

// Opciones de configuración estática de CORS
const corsOptions = {
  origin: ['https://mi-frontend.com', 'https://mi-admin-dashboard.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  optionsSuccessStatus: 200 // Habilita soporte para navegadores antiguos
};

// Registrar el middleware globalmente
app.use(cors(corsOptions));

app.get('/api/data', (req, res) => {
  res.json({ message: "CORS configurado con éxito" });
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

2. Configuración Dinámica con Whitelist (Evitar conflictos con Credentials)

Como vimos en los gotchas de seguridad, si necesitas habilitar credentials: true, no puedes usar el wildcard (*). Si tu lista de orígenes debe verificarse dinámicamente, puedes configurar origin como una función:

const express = require('express');
const cors = require('cors');
const app = express();

// Lista de dominios permitidos (Whitelist)
const whitelist = ['https://mi-frontend.com', 'https://mi-admin-dashboard.com'];

const corsOptions = {
  origin: function (origin, callback) {
    // Permitir peticiones sin origen (como curl o llamadas del mismo servidor)
    // o verificar si el origen entrante está en la lista blanca
    if (!origin || whitelist.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('Bloqueado por políticas de CORS de la API'));
    }
  },
  credentials: true, // Habilitar soporte para cookies y cabeceras de autorización
  optionsSuccessStatus: 200
};

// Aplicar el middleware de CORS con validación dinámica
app.use(cors(corsOptions));

app.get('/api/data', (req, res) => {
  res.json({ message: "CORS dinámico configurado con éxito" });
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Casos de Uso Comunes

  • Frontend y Backend desacoplados: El caso clásico donde tu aplicación web SPA (ej. React/Vue/Angular) está en https://app.mi-dominio.com y necesita consumir la API de datos alojada en https://api.mi-dominio.com.
  • Integraciones externas en widgets: Scripts de chat en vivo, pasarelas de pago incrustadas o widgets que cargan dinámicamente y se comunican con sus respectivos servidores de origen para procesar interacciones del usuario.
  • CDNs y fuentes personalizadas: Navegadores que cargan tipografías web (.woff2) o archivos de assets estáticos desde servidores web de entrega de contenido (CDNs) independientes.
  • APIs de datos públicos: Servicios libres (como mapas, clima, divisas) que pretenden ser consumidos de forma abierta en el navegador mediante el comodín Access-Control-Allow-Origin: *.

Para no morir en el intento y consejos que no pediste

Configurar CORS es una de las mayores fuentes de dolores de cabeza para los desarrolladores. Conocer estos secretos te evitará atascarte en problemas repetitivos:

1. El gran conflicto: Credentials + Wildcard

Si tu aplicación requiere enviar sesiones basadas en cookies o cabeceras HTTP específicas como Authorization, debes habilitar la cabecera Access-Control-Allow-Credentials: true.

Sin embargo, el estándar prohíbe terminantemente combinar credentials con un wildcard (*) en Access-Control-Allow-Origin. Si intentas hacerlo, el navegador bloqueará inmediatamente la respuesta.

  • Cómo resolverlo: Tu servidor debe leer dinámicamente el valor del encabezado Origin de la solicitud entrante, comprobar si está incluido en tu lista de dominios seguros autorizados y, en caso positivo, retornar ese dominio específico en la cabecera Access-Control-Allow-Origin.

2. Cuida el rendimiento: Configura Access-Control-Max-Age

Por cada petición REST compleja (métodos PUT, DELETE o envíos de application/json), el navegador emite primero un preflight de tipo OPTIONS. Esto duplica la carga en tu API y añade latencia innecesaria en la carga del sitio.

  • Cómo resolverlo: Añade la cabecera Access-Control-Max-Age indicando los segundos que el navegador puede conservar en caché la aprobación del preflight. Un valor de 86400 (24 horas) o 3600 (1 hora) optimiza radicalmente la velocidad de navegación del usuario.

3. La trampa de las pruebas con curl y Postman

Si experimentas un error de CORS en la consola del navegador, no intentes diagnosticarlo usando Postman o curl para ver si la petición falla de la misma manera.

  • Cómo resolverlo: CORS es un control del lado del cliente. Los clientes HTTP no integrados en navegadores ignoran por completo estas políticas. Concéntrate en inspeccionar la pestaña Network de las herramientas de desarrollo de tu navegador y confirma las cabeceras inyectadas por el servidor.

Reflexión Final

CORS no es un error de código ni una barrera molesta, sino una protección de primera línea para garantizar la privacidad y los datos de las sesiones de los usuarios en la web abierta. Una correcta comprensión de su flujo de negociación evita vulnerabilidades y optimiza el rendimiento.

Para llevarte a casa:

  1. CORS protege a tus usuarios, no a tu base de datos de atacantes externos (para eso usa JWT/API Keys).
  2. Nunca mezcles Credentials con comodines * para evitar fallos de seguridad críticos en tu API.
  3. Configura Access-Control-Max-Age en producción para evitar duplicar el tráfico de tu backend.

Top comments (0)