CORS explicado: preflight, cabeceras y cómo solucionar errores CORS

CORS explicado

CORS explicado: preflight, cabeceras, errores y soluciones | We.Inc

Cómo funciona CORS: peticiones simples y preflight, cada cabecera Access-Control, reglas de credenciales, los errores de consola habituales y una solución en el servidor para Express, Next.js y nginx.

CORS (intercambio de recursos de origen cruzado) es la forma en que un servidor le dice al navegador que el JavaScript de otro origen puede leer sus respuestas. El servidor responde con Access-Control-Allow-Origin y cabeceras relacionadas. Las peticiones con otros métodos, cabeceras personalizadas o cuerpos JSON envían antes un preflight OPTIONS. La solución siempre está en el servidor al que llamas, nunca en la página.

Las cabeceras de respuesta CORS de un vistazo

Un error CORS parece indicar que tu código está roto, pero normalmente la petición llegó al servidor sin problema. El navegador simplemente se negó a entregar la respuesta a tu script. El simulador de abajo te permite describir una petición y la configuración del servidor, y te dice si se lanza un preflight, si el navegador deja pasar la respuesta y qué mensaje verías en la consola.

Preguntas frecuentes

¿Por qué funciona en Postman o curl y falla en el navegador?

CORS solo lo aplican los navegadores, para impedir que los scripts de un sitio lean los datos de otro sitio con tus cookies. Herramientas como curl no ejecutan código de páginas no confiables, así que lo ignoran. La respuesta del servidor era idéntica; solo el navegador decidió retenerla.

¿Qué provoca una petición preflight?

Cualquier método distinto de GET, HEAD o POST, cualquier cabecera fuera de la pequeña lista segura (Accept, Accept-Language, Content-Language, Content-Type, Range), o un Content-Type distinto de form encoding, multipart o text/plain. Enviar application/json es la causa más común.

¿Puedo solucionar CORS desde el frontend?

No. Poner cabeceras Access-Control en tu petición no sirve de nada, y mode: no-cors te da una respuesta opaca que tu script no puede leer. O la API añade las cabeceras, o tu propio servidor obtiene los datos y te los pasa.

¿Puedo usar Access-Control-Allow-Origin: * con cookies?

No. Cuando una petición lleva credenciales, el navegador rechaza un origen comodín y exige el origen exacto más Access-Control-Allow-Credentials: true. Lo mismo vale para los comodines en las listas de métodos, cabeceras y cabeceras expuestas.

¿Por qué mi script no puede leer una cabecera de respuesta personalizada?

Por defecto, un script de origen cruzado solo ve una lista corta de cabeceras de respuesta seguras. Incluye las demás en Access-Control-Expose-Headers, por ejemplo X-Request-Id, y pasarán a ser legibles.

Mismo origen, y por qué el navegador bloquea las lecturas

Peticiones simples y preflights

Cómo leer el error de la consola

Errores CORS comunes y cómo solucionarlos

El texto del mensaje varía según el navegador. Estos son los motivos tal como los nombra Firefox en MDN; Chrome dice lo mismo con frases más largas.

Motivos CORS en la consola y la solución en el servidor

Fragmentos de servidor

Express: lista de permitidos con credenciales

Devuelve el origen solo si está en la lista, responde a los preflights con 204 y añade Vary para que las cachés mantengan separados los orígenes.

Route handler de Next.js

Un endpoint público y sin cookies puede usar el comodín. Exporta OPTIONS para que el preflight tenga su propia respuesta.

nginx delante de una API

El indicador always mantiene la cabecera también en las respuestas de error, así un 500 muestra su estado real en lugar de un error CORS.

El lado del navegador: enviar cookies entre orígenes

credentials: "include" es lo que activa en el servidor las reglas de credenciales más estrictas.

Simulador de peticiones CORS

Describe una petición y las cabeceras CORS del servidor para ver si se envía un preflight y si el navegador deja que tu script lea la respuesta.

La petición

La respuesta del servidor

Origen de la página

Origen de la API

Método

Content-Type

Sin cuerpo

Cabecera de petición extra (vacío para ninguna)

Enviar cookies (credentials: include)

Access-Control-Allow-Origin

Access-Control-Allow-Methods

Access-Control-Allow-Headers

Access-Control-Allow-Credentials: true

Veredicto

Mismo origen: CORS no se aplica, la respuesta es legible.

Sin preflight: es una petición simple.

Hace falta preflight: el navegador envía OPTIONS primero.

Motivo: el método no es GET, HEAD ni POST.

Motivo: la cabecera {v} no está en la lista segura.

Motivo: el Content-Type {v} no es uno de los tres tipos simples.

Permitido: tu script puede leer la respuesta.

Bloqueado. Motivo en la consola:

Cabeceras que debería enviar el servidor

Empieza gratis · Sin tarjeta de crédito

Relacionado: Guías

Producto

Para quién es

Funciones

Recursos

Empresa

Mapa del sitio