Inspector CORS

Painel de diagnóstico CORS

O servidor simula a solicitação preflight e a solicitação real para determinar rapidamente se o problema está em Allow-Origin, Allow-Headers ou credentials.

Simula en servidor solicitudes preflight CORS y solicitudes reales, verifica las 6 cabeceras de respuesta una por una, localiza con precisión errores de configuración cross-origin y proporciona código de corrección para múltiples frameworks como Nginx/Node.js/Spring.

Sugestões Relacionadas

Casos de uso

  • Cuando aparece el error No 'Access-Control-Allow-Origin' header en la consola del navegador, usa inmediatamente esta herramienta para verificar las cabeceras CORS que realmente devuelve la API objetivo
  • Durante la fase de integración frontend-backend en proyectos separados, verifica que la configuración CORS del backend funcione correctamente, evitando perder tiempo cambiando configuración a ciegas
  • Después de configurar proxy inverso Nginx, Apache o API Gateway (Kong/APISIX/Spring Cloud Gateway), verifica que las reglas CORS se transmitan correctamente
  • Cuando fallan solicitudes cross-origin que llevan Cookie, alterna el modo credentials para detectar conflictos de configuración entre Allow-Credentials y Allow-Origin
  • Después de añadir cabeceras personalizadas (como X-Token, X-Requested-With) la solicitud es bloqueada, verifica si están correctamente declaradas en Allow-Headers
  • Solicitudes con métodos no simples como PUT/DELETE/PATCH fallan en CORS, verifica si Allow-Methods incluye el método correspondiente
  • El frontend no puede leer cabeceras de respuesta personalizadas (como X-Request-Id, X-Total-Count), verifica la configuración de Access-Control-Expose-Headers
  • Después de acelerar con CDN, CORS funciona intermitentemente, verifica si Vary: Origin está configurado correctamente para evitar que CDN cachee respuestas incorrectas
  • Errores CORS ocasionales en producción, reproduce el escenario del problema y conserva el mensaje de respuesta completo para que el backend lo depure
  • Al aprender los principios de CORS, observa la función de cada cabecera de respuesta mediante solicitudes reales, profundizando la comprensión del mecanismo cross-origin

Como Usar

  1. En el campo de URL de destino, escribe la dirección completa de la API a verificar (soporta http/https, debe incluir la ruta)
  2. En el campo Origin de solicitud, escribe el origen real de tu página frontend (como https://example.com, importante incluir protocolo y puerto, sin ruta)
  3. Selecciona el método HTTP (GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS), por defecto GET
  4. En el área de cabeceras personalizadas, añade las cabeceras que necesites enviar, una por línea en formato X-Token: abc123. Content-Type con tipos no simples como application/json disparará automáticamente preflight
  5. Marca la opción "Con credenciales (credentials)" según tu escenario; si tu código frontend usa withCredentials=true o credentials: 'include' en fetch, debes marcarla
  6. Haz clic en el botón "Iniciar verificación", la herramienta enviará secuencialmente desde el servidor la solicitud preflight OPTIONS y la solicitud real (si el preflight pasa o no es necesario)
  7. Revisa el informe de análisis: primero mira el resultado de evaluación general, luego verifica el estado de cada cabecera CORS una por una, ajusta la configuración del servidor según las recomendaciones de corrección de los errores en rojo, vuelve a verificar después de corregir

Recursos

  • Simulación real en servidor de solicitudes preflight y solicitudes reales, sin interferencias de caché del navegador ni extensiones, resultados más precisos
  • Muestra por separado la respuesta de preflight OPTIONS y la respuesta real GET/POST, distinguiendo claramente en qué etapa ocurre el problema
  • Verifica las 6 cabeceras CORS principales una por una: Access-Control-Allow-Origin, Allow-Methods, Allow-Headers, Allow-Credentials, Expose-Headers, Max-Age, cada cabecera marcada individualmente con estado de aprobación/advertencia/error
  • Detecta inteligentemente el conflicto clásico entre comodín * y modo credentials, alertando inmediatamente sobre esta trampa de configuración más común
  • Soporta origen de solicitud Origin personalizado, métodos HTTP (GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS) y cabeceras de solicitud personalizadas arbitrarias
  • Permite alternar el modo con o sin credenciales (Cookie/cabecera Authorization/certificados cliente TLS), simulando escenarios con withCredentials=true
  • Detecta automáticamente si la cabecera Vary: Origin está configurada correctamente, evitando que CDN/proxy inverso cacheen respuestas CORS incorrectas
  • Muestra estructuradamente la configuración de caché preflight Max-Age, evaluando el impacto de la frecuencia de solicitudes preflight en el rendimiento
  • Verifica la configuración de Access-Control-Expose-Headers, confirmando qué cabeceras de respuesta pueden ser leídas por JavaScript en el frontend
  • Proporciona recomendaciones de corrección línea por línea, incluyendo ejemplos de configuración para frameworks servidor principales como Nginx, Apache, Node.js/Express, Spring Boot, Python/Django/Flask
  • Registra completamente los mensajes originales de solicitud y respuesta, incluyendo código de estado, todas las cabeceras de respuesta, vista previa del cuerpo de respuesta, facilitando la depuración profunda
  • Identifica automáticamente las diferencias entre solicitudes simples y solicitudes que requieren preflight, explicando por qué tu solicitud dispara el preflight OPTIONS
  • Detecta problemas CORS en escenarios de redirección (si cambia el Origin después de redirecciones 301/302/307/308)
  • Soporta detección de escenarios de contenido mixto HTTPS/HTTP, alertando sobre problemas relacionados con CORS causados por contenido mixto

Perguntas frequentes

¿Por qué con Postman/curl la API funciona bien, pero cuando accede el navegador reporta error cross-origin?

¡Este es el problema CORS más clásico! Porque Postman y curl no están sujetos en absoluto a la política del mismo origen del navegador — son clientes HTTP, no navegadores, no interceptan respuestas, y tampoco envían automáticamente solicitudes preflight OPTIONS. Los errores cross-origin son un mecanismo de seguridad exclusivo del navegador; solo el entorno de ejecución JavaScript del navegador realiza comprobaciones CORS. Que Postman pueda conectar solo demuestra que la API en sí puede devolver datos, no demuestra que la configuración CORS sea correcta. Necesitas usar el navegador, o esta herramienta (que simula el flujo CORS del navegador en el servidor) para verificar, y ahí descubrirás el problema.

¿El error CORS es problema del frontend o del backend? ¿Quién debe arreglarlo?

El 99% de los errores CORS son problemas de configuración del backend (o problemas de configuración en capa Nginx/gateway), el frontend puede hacer muy poco. El núcleo de CORS es que el servidor "autoriza" al navegador mediante cabeceras de respuesta para permitir el acceso cross-origin; el frontend solo puede configurar withCredentials, establecer cabeceras de solicitud, etc., no puede eludir las restricciones de seguridad del navegador. Los proxies del frontend de los que se habla en internet (proxy devServer, proxy inverso Nginx que proxifican frontend y API al mismo origen) esencialmente "engañan" al navegador haciéndole creer que es una solicitud mismo origen, no resuelven realmente el problema CORS; en producción si frontend y backend son diferentes orígenes el backend aún necesita configurar CORS correctamente.

¿Por qué mi solicitud GET no tiene cross-origin, pero en cuanto la cambio a POST/PUT aparece cross-origin?

Porque GET normalmente cumple las condiciones de solicitud simple, el navegador envía la solicitud directamente; mientras que POST si envías Content-Type: application/json (el 90% de las APIs POST son esto), ya no pertenece a solicitud simple, disparará preflight OPTIONS. La solicitud preflight requiere que el servidor devuelva las cabeceras CORS correctas; mucha gente solo configuró cabeceras CORS para GET/POST pero no manejó el método OPTIONS, o la respuesta OPTIONS no tiene cabeceras, por lo que reporta error. Los métodos PUT/DELETE/PATCH por sí mismos no son métodos simples, inevitablemente disparan preflight.

¿Cómo resolver cross-origin en entorno de desarrollo? ¿Y en producción?

En entorno de desarrollo hay varias soluciones: ① Proxy integrado en el framework (devServer.proxy de Vue CLI, server.proxy de Vite, proxy de Create React App): proxifica las solicitudes de API al mismo origen que el servidor de desarrollo del frontend, el navegador cree que es solicitud mismo origen sin cross-origin; ② Desactivar parámetros de seguridad del navegador (por ejemplo Chrome con parámetro de inicio --disable-web-security), solo para pruebas temporales locales, absolutamente no para navegación normal; ③ Configurar CORS en el backend permitiendo origen localhost (recomendado, comportamiento consistente con producción). En producción el backend debe configurar CORS correctamente: configurar lista blanca de orígenes permitidos, establecer correctamente Allow-Methods/Allow-Headers/Allow-Credentials, añadir Vary: Origin, o usar gateway/Nginx para manejar CORS de forma unificada.

¿Se puede configurar Access-Control-Allow-Origin con múltiples orígenes? ¿Cómo configurar múltiples dominios para permitir cross-origin?

No, la cabecera de respuesta Access-Control-Allow-Origin solo puede tener un valor, ya sea un origen específico (como https://a.com), o *. El navegador no acepta múltiples orígenes (por ejemplo escribir https://a.com,https://b.com es inválido, el navegador lo considerará no coincidente). La forma correcta de configuración multi-origen es: el servidor mantiene una lista blanca de orígenes permitidos, cada vez que recibe una solicitud lee el valor de Origin en la cabecera de solicitud, verifica si ese Origin está en la lista blanca, si es así establece Access-Control-Allow-Origin como ese valor de Origin específico, y devuelve al mismo tiempo la cabecera Vary: Origin; si no está, no devuelve cabeceras CORS o devuelve error. No intentes devolver múltiples cabeceras Allow-Origin o separar múltiples valores por coma, el navegador no los reconoce.

¿La solicitud preflight OPTIONS necesita devolver lógica de negocio? ¿Puede devolver directamente 204?

La solicitud preflight OPTIONS no necesita devolver ninguna lógica de negocio ni cuerpo de respuesta, solo necesita devolver las cabeceras de respuesta CORS correctas y código de estado 200/204 es suficiente. El navegador al recibir las cabeceras CORS correctas considera que el preflight pasó, no leerá el contenido del body de la respuesta OPTIONS. Así que la mejor práctica es: interceptar directamente solicitudes OPTIONS en la capa Nginx/gateway, devolver 204 No Content y las cabeceras CORS correctas, sin reenviar al servidor de aplicaciones backend; esto aligera la carga del backend y también evita errores 405 causados por rutas backend que no soportan OPTIONS. Pero asegúrate de que las cabeceras CORS de la respuesta OPTIONS sean consistentes con las cabeceras CORS de la solicitud real.

¿Por qué configuré withCredentials: true, pero Cookie no se lleva?

Las solicitudes cross-origin con Cookie requieren cumplir simultáneamente tres condiciones: ① XMLHttpRequest.withCredentials = true en el frontend o credentials: 'include' en fetch; ② Cabecera de respuesta backend Access-Control-Allow-Credentials: true; ③ Access-Control-Allow-Origin no puede ser *, debe ser origen específico. Las tres condiciones son indispensables. Además presta atención a los atributos de Cookie: la Cookie debe tener establecido SameSite=None; Secure (entorno HTTPS) para poder llevarse en solicitudes cross-origin; si la Cookie es SameSite=Lax o Strict, en solicitudes cross-origin no se llevará; el atributo Domain de la Cookie debe estar configurado correctamente; también presta atención al impacto de las políticas de Cookie de terceros (navegadores como Chrome tienen restricciones sobre Cookies de terceros).

¿Qué relación hay entre CORS y CSRF? ¿Configurar CORS causará vulnerabilidades CSRF?

CORS es relajar las restricciones de acceso cross-origin, CSRF es ataque de falsificación de solicitudes entre sitios, son conceptos diferentes. Configurar CORS correctamente no causa directamente vulnerabilidades CSRF — porque CORS solo permite que JS lea respuestas, mientras que CSRF es cuando un atacante induce al usuario a enviar solicitudes sin saberlo (como etiquetas img, envío automático de formularios), estas solicitudes incluso sin CORS se envían llevando Cookie. Defender CSRF requiere usar CSRF Token, SameSite Cookie, verificar Origin/Referer y otras soluciones, no se puede defender CSRF deshabilitando CORS. Pero si al configurar CORS estableces Allow-Origin como * y permites credenciales, sí amplificará el riesgo CSRF, por eso en escenarios con credenciales absolutamente no se puede usar *, debes restringir estrictamente la lista blanca de orígenes.

¿La descarga de archivos, redirecciones, carga de etiquetas img/script tienen problemas CORS?

La carga de recursos cross-origin con etiquetas <img>, <script>, <link> normales (imágenes CDN, scripts JS, CSS) por defecto no tiene problemas CORS; estos son "recursos incrustados" no "solicitudes AJAX", el navegador permite cargarlos pero JS no puede leer el contenido. Pero si quieres manipular imágenes cross-origin en Canvas, o cargar estos recursos con fetch/XHR y leer el contenido, entonces necesitas CORS, y al mismo tiempo debes añadir el atributo crossorigin en la etiqueta. La descarga de archivos cross-origin (clic en etiqueta a para descargar) por defecto tampoco necesita CORS. Las redirecciones afectan CORS: si la solicitud preflight OPTIONS devuelve redirección 3xx, el navegador la rechazará directamente (Preflight redirect is not allowed); si la redirección de la solicitud real es entre orígenes diferentes, cada salto necesita devolver cabeceras CORS correctamente.

¿Cómo configurar CORS correctamente en Nginx? ¿Dan un ejemplo de configuración funcional?

Puntos clave de configuración recomendada en Nginx: ① Usar directiva map para coincidir con lista blanca de Origins, estableciendo dinámicamente la variable $cors_origin; ② Las solicitudes OPTIONS devuelven directamente 204 sin reenviar al backend; ③ add_header recuerda añadir el parámetro always para asegurar que respuestas de error también lleven cabecera; ④ Añadir Vary: Origin; ⑤ Configurar correctamente Allow-Methods/Allow-Headers/Credentials. Puedes encontrar configuraciones de ejemplo en las recomendaciones de corrección de los resultados de detección de esta herramienta; proporcionamos fragmentos de configuración verificados para frameworks principales como Nginx, Apache, Node.js Express, Spring Boot, Python Flask/Django, Koa, Go Gin.

Después de configurar CORS, ¿cómo verificar si tiene efecto?

Pasos para verificar CORS: ① Primero usa la detección online de esta herramienta, introduce URL, Origin, método, cabeceras, opción de credenciales, mira si cada comprobación del preflight y la solicitud real pasan; ② Abre el panel Network de DevTools del navegador, marca Disable cache para deshabilitar caché (evitando interferencia de caché antiguo), dispara la solicitud cross-origin, verifica que las cabeceras de respuesta del preflight OPTIONS y la solicitud real sean correctas; ③ En Console mira si hay errores relacionados con CORS; ④ Prueba diferentes escenarios: solicitudes GET sin cabeceras personalizadas, solicitudes POST con application/json, métodos PUT/DELETE, con Cookie/sin Cookie, con cabeceras personalizadas; ⑤ Simula solicitud OPTIONS con curl: curl -i -X OPTIONS -H "Origin: https://yoursource.com" https://api.example.com/endpoint, verifica que las cabeceras de respuesta sean correctas.

¿Por qué la solicitud cross-origin reporta error en Chrome pero funciona normalmente en Safari/Firefox? ¿O al revés?

Diferentes navegadores tienen diferencias en los detalles de implementación de la especificación CORS: ① Safari tiene restricciones más estrictas sobre Max-Age del caché preflight (versiones antiguas solo 600 segundos), la política de Cookies (ITP anti-rastreo inteligente) también es más estricta, puede causar problemas de Cookie cross-origin; ② Chrome tiene comprobaciones más estrictas sobre comodines con credenciales, Allow-Headers/Allow-Methods tampoco permiten *, algunas versiones de Firefox pueden ser más laxas; ③ IE antiguo (IE11) usa XDomainRequest en lugar de XMLHttpRequest estándar, la implementación CORS tiene muchas trampas (por ejemplo no soporta cabeceras personalizadas, solo soporta GET/POST); ④ Diferentes navegadores tienen diferencias sutiles en el manejo de cabecera Vary, manejo de redirecciones, manejo de cabeceras de seguridad. La solución es configurar estrictamente según la especificación CORS, no depender del comportamiento compatible de un navegador específico.

¿Cuánto es apropiado establecer Access-Control-Max-Age? ¿Qué problemas hay si se establece demasiado largo o demasiado corto?

Se recomienda establecer entre 3600 (1 hora) y 86400 (24 horas). Establecer demasiado corto (como 60 segundos): el caché preflight expira rápidamente, enviando frecuentemente solicitudes OPTIONS, aumentando el tiempo de solicitud y la presión del servidor, el impacto es notable en entornos de red móvil débil. Establecer demasiado largo (como 31536000, es decir un año): si actualizas la configuración CORS (por ejemplo añadiendo métodos permitidos, cabeceras), el resultado preflight antiguo en caché del navegador del usuario puede tardar mucho en expirar, durante ese periodo aparecerán problemas de que la configuración no tiene efecto. Además Chrome y Firefox truncarán automáticamente los valores que superen 86400 segundos a 86400 segundos, por más que lo establezcas no sirve de nada. En entorno de desarrollo puedes establecer -1 para deshabilitar el caché preflight, facilitando la depuración.

¿Las conexiones WebSocket tienen problemas cross-origin? ¿CORS aplica a WebSocket?

WebSocket (ws:// y wss://) no están sujetos al mecanismo CORS HTTP, porque WebSocket es un protocolo independiente, no son solicitudes HTTP AJAX. Pero la fase de handshake de WebSocket es una solicitud HTTP; el servidor puede verificar la cabecera Origin para decidir si permite la conexión — esto es control de permisos a nivel de WebSocket, no es CORS estándar, pero el principio es similar. Si la conexión WebSocket es rechazada, necesitas verificar la configuración de verificación Origin del servidor WebSocket, no las cabeceras CORS HTTP. Librerías como Socket.IO pueden tener su propia forma de configuración cross-origin, similar a la configuración CORS estándar pero no exactamente igual. Además, otras APIs del navegador como HTTP/2 Server Push, WebRTC también tienen su propio control de permisos, no siguen completamente el CORS HTTP.

¿Los resultados de detección de esta herramienta son consistentes con el comportamiento del navegador? ¿Por qué con la herramienta la detección pasa pero el navegador sigue reportando error?

Esta herramienta en el servidor simula estrictamente según la especificación W3C CORS el flujo de preflight y solicitud real del navegador, la lógica de comprobación de cabeceras CORS es básicamente consistente con los navegadores modernos. Si la herramienta detecta que pasa pero el navegador sigue reportando error, las posibles causas son: ① El navegador cacheó resultados preflight o respuestas antiguas, haz recarga forzada Ctrl+F5 o borra caché y vuelve a intentar; ② Extensiones del navegador (bloqueo de publicidad, protección de privacidad, plugins de seguridad) modificaron o interceptaron la solicitud; ③ El Origin, cabeceras de solicitud, método, opción de credenciales que escribiste en la herramienta no son consistentes con los que realmente envía el frontend (por ejemplo el frontend realmente lleva alguna cabecera personalizada que no añadiste en la herramienta); ④ Caché inconsistente en nodos CDN/proxy, el nodo al que la herramienta solicita y el nodo al que el navegador solicita devuelven resultados diferentes; ⑤ El navegador tiene políticas de seguridad especiales (como solicitudes a contenido mixto HTTP en páginas HTTPS son bloqueadas, restricciones especiales del protocolo file:// de archivos locales).

Solução de Problemas

La consola del navegador reporta error No 'Access-Control-Allow-Origin' header, pero con curl/postman la solicitud puede ver las cabeceras de respuesta

El servidor no tiene configuradas cabeceras CORS para el método OPTIONS: curl envía GET/POST puede ver las cabeceras, pero el navegador envía primero preflight OPTIONS, y la respuesta OPTIONS no tiene cabeceras Cuando el servidor devuelve errores 4xx/5xx no lleva cabeceras CORS: add_header de Nginx por defecto solo tiene efecto para 2xx y algunos 3xx, respuestas de error necesitan añadir el parámetro always La solicitud fue bloqueada por WAF/firewall/plugin de seguridad en el método OPTIONS, la respuesta de bloqueo no tiene cabeceras CORS CDN cacheó una respuesta antigua sin cabeceras CORS (contaminación de caché causada por falta de Vary: Origin) Extensiones del navegador (como bloqueadores de publicidad, plugins de protección de privacidad) modificaron o interceptaron las cabeceras de solicitud cross-origin

Configuré Access-Control-Allow-Origin: *, pero las solicitudes con Cookie siguen fallando

Esta es una restricción obligatoria de la especificación CORS: mientras la solicitud lleve credenciales (withCredentials: true, Cookie, autenticación HTTP), Allow-Origin absolutamente no puede ser comodín * Incluso si el frontend no estableció explícitamente withCredentials, si el dominio objetivo y el dominio actual tienen Cookie, el navegador puede llevarla automáticamente No solo Allow-Origin no puede ser *, Allow-Headers y Allow-Methods en algunos navegadores con credenciales tampoco pueden usar * Necesitas establecer simultáneamente Access-Control-Allow-Credentials: true, y Allow-Origin devolver el origen de la solicitud específico en lugar de *

La solicitud preflight OPTIONS devuelve 405 Method Not Allowed o 404 Not Found

Las rutas del framework backend solo configuraron métodos de negocio como GET/POST, no hay rutas que manejen el método OPTIONS En la configuración de Nginx la directiva try_files interceptó la solicitud OPTIONS, devolviendo directamente 404 sin reenviar al backend Configuraciones de API Gateway o grupos de seguridad bloquearon el método OPTIONS, considerándolo método "inútil" Frameworks como Spring Boot si no tienen habilitado el soporte CORS, rechazarán automáticamente solicitudes OPTIONS devolviendo 403 En el diseño de API RESTful no se configuró un handler independiente para solicitudes OPTIONS

Solicitudes POST application/json siempre reportan que la cabecera no está permitida, pero Content-Type ya está añadido en Allow-Headers

Error de escritura: mayúsculas/minúsculas o guion mal escritos en Content-Type (por ejemplo escribir ContentType, Content-type, aunque la especificación no distingue mayúsculas/minúsculas algunos servidores con coincidencia estricta tienen problemas) Solo se añadió la cabecera en respuestas GET/POST, pero la respuesta preflight OPTIONS no la tiene; el navegador mira las cabeceras de OPTIONS Las cabeceras listadas en Allow-Headers no están completas: por ejemplo el frontend también envía X-Requested-With u otras cabeceras personalizadas no listadas Valores con espacios extra: por ejemplo escribir "Content-Type, Authorization" (espacio después de coma es aceptable, pero algunos navegadores antiguos tienen problemas al analizar) Access-Control-Allow-Headers solo devuelve las cabeceras preguntadas en la solicitud preflight, en lugar de todas las cabeceras soportadas (aunque la especificación lo permite, en algunos escenarios causa problemas)

La configuración cross-origin funciona bien en pruebas locales, después de desplegar a CDN/Nginx funciona intermitentemente con errores aleatorios

¡Falta la cabecera de respuesta Vary: Origin! CDN cachea por URL, la primera solicitud con cabeceras CORS es cacheada, solicitudes posteriores de diferentes Origins reciben respuesta cacheada incorrecta CDN configuró reglas de caché que también cachean la respuesta preflight OPTIONS, después de actualizar configuración CORS en backend CDN sigue teniendo caché antiguo Nginx tiene múltiples capas de add_header, las cabeceras CORS de capas inferiores son sobrescritas por capas superiores o hay errores al fusionar La función de optimización de cabeceras HTTP del CDN elimina o sobrescribe automáticamente cabeceras Access-Control-* Contenido mixto HTTPS/HTTP: la página es HTTPS pero la API es HTTP, el navegador la bloquea directamente, pareciendo error CORS Configuración inconsistente entre nodos CDN, algunos nodos tienen cabeceras CORS y otros no

El frontend puede obtener el código de estado y datos de la respuesta, pero no puede leer cabeceras de respuesta personalizadas (como X-Total-Count)

¡No está configurada la cabecera de respuesta Access-Control-Expose-Headers! Por defecto la especificación CORS solo expone unas pocas cabeceras de respuesta básicas para que JS las lea Las cabeceras de respuesta accesibles por defecto son solo: Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified, Pragma Cabeceras de respuesta personalizadas (X-Request-Id, X-Total-Count, Authorization, Set-Cookie etc.) deben listarse en Expose-Headers Set-Cookie y Set-Cookie2 nunca serán expuestas a JS del frontend; es una restricción de seguridad del navegador, no importa cómo lo configures Incluso si las listas en Expose-Headers, Set-Cookie no se puede leer mediante getResponseHeader, el navegador la filtrará automáticamente

Glossário

CORS (Cross-Origin Resource Sharing)
Compartición de Recursos de Origen Cruzado, estándar W3C, que permite al servidor declarar qué orígenes pueden acceder a recursos mediante nuevos campos de cabecera HTTP, es la solución oficial de los navegadores modernos para resolver problemas cross-origin.
Política del Mismo Origen (Same-Origin Policy)
Mecanismo de seguridad central del navegador; solo cuando protocolo, dominio y puerto son exactamente iguales se considera mismo origen, y JavaScript de orígenes diferentes por defecto no puede leer los recursos del otro.
Solicitud Preflight (Preflight Request)
Solicitud con método OPTIONS que el navegador envía automáticamente primero para solicitudes cross-origin no simples, usada para preguntar al servidor si permite la solicitud real posterior; solo después de pasar el preflight se envía la solicitud verdadera.
Solicitud Simple (Simple Request)
Solicitud cross-origin que cumple condiciones específicas (método GET/HEAD/POST, solo cabeceras seguras, Content-Type específico), que no necesita enviar preflight y envía la solicitud real directamente.
Access-Control-Allow-Origin (ACAO)
Cabecera de respuesta CORS principal, especifica el origen permitido para acceder al recurso, puede ser un URI de origen específico o comodín *, con credenciales no se puede usar *.
Access-Control-Allow-Methods (ACAM)
Cabecera de respuesta preflight, lista todos los métodos HTTP soportados por el servidor, múltiples métodos separados por coma.
Access-Control-Allow-Headers (ACAH)
Cabecera de respuesta preflight, lista todos los campos de cabecera de solicitud permitidos por el servidor; las cabeceras personalizadas enviadas por el frontend deben declararse aquí.
Access-Control-Allow-Credentials (ACAC)
Cabecera de respuesta, valor booleano true indica que se permite llevar credenciales como Cookie y Authorization en solicitudes cross-origin; en este caso Allow-Origin no puede ser *.
Access-Control-Max-Age (ACMA)
Cabecera de respuesta preflight, especifica el tiempo de caché del resultado preflight en segundos; una configuración razonable puede reducir solicitudes OPTIONS mejorando el rendimiento.
Vary: Origin
Cabecera de respuesta, le dice a CDN/proxy que el contenido de la respuesta varía según la cabecera Origin, al cachear debe incluir Origin como clave de caché para evitar desorden de caché de respuestas cross-origin.
Cabeceras de solicitud de lista segura CORS (CORS-safelisted request headers)
Cabeceras de solicitud que se pueden enviar sin necesidad de declararlas en Allow-Headers, incluyendo Accept, Accept-Language, Content-Language, Content-Type (valores específicos), etc.
Nombres de cabecera prohibidos (Forbidden header name)
Cabeceras de solicitud que el navegador prohíbe a JavaScript establecer mediante programación, como Host, Connection, Cookie, Origin, etc.; estas cabeceras son controladas automáticamente por el navegador.
Solicitud con credenciales (Credentialed Request)
Solicitud cross-origin que lleva credenciales de identidad como Cookie, información de autenticación HTTP, certificados cliente TLS; requiere que frontend y backend configuren conjuntamente withCredentials y Allow-Credentials.
Access-Control-Expose-Headers
Cabecera de respuesta, lista las cabeceras de respuesta a las que JavaScript puede acceder; por defecto solo unas pocas cabeceras básicas son accesibles, las cabeceras personalizadas deben declararse aquí.
Método OPTIONS
Uno de los métodos HTTP, usado para obtener las opciones de comunicación soportadas por el servidor; CORS lo usa para enviar solicitudes preflight, preguntando al servidor los métodos, cabeceras, credenciales permitidos.
Cabecera de solicitud Origin
Cabecera de solicitud añadida automáticamente por el navegador, indica de qué origen proviene la solicitud actual (protocolo+dominio+puerto); el servidor determina si permite cross-origin según esta cabecera.
Atributo crossorigin
Atributo de elementos HTML (como script, img, link), usado para especificar si se habilita la carga de recursos con CORS; los valores son anonymous (sin credenciales) y use-credentials (con credenciales).
Modo de solicitud no-cors
Un mode de la API fetch, que permite enviar solicitudes cross-origin pero solo puede enviar solicitudes simples y JavaScript no puede leer el contenido de la respuesta, equivalente a enviar una solicitud opaca (Opaque Response).

Tabla de comparación completa de cabeceras de respuesta CORS

Cabecera de respuestaFunciónEjemplo de valorNotas importantes
Access-Control-Allow-OriginEspecifica el origen permitido para acceso cross-originhttps://example.com o *Con credenciales absolutamente no se puede usar *, debe devolver Origin específico; devolución dinámica debe añadir Vary: Origin
Access-Control-Allow-MethodsLista todos los métodos HTTP soportados por el servidorGET, POST, PUT, DELETE, PATCH, OPTIONSObligatoria en respuesta preflight; hay que listar todos los métodos, no solo el método de la solicitud actual
Access-Control-Allow-HeadersLista todos los campos de cabecera de solicitud permitidosContent-Type, Authorization, X-TokenObligatoria cuando la solicitud preflight tiene Access-Control-Request-Headers; cabeceras personalizadas deben incluirse; con credenciales algunos navegadores no permiten *
Access-Control-Allow-CredentialsSi se permiten credenciales (Cookie etc.)trueSolo puede ser la cadena "true" en minúsculas, no 1 ni True; al establecerse en true Allow-Origin no puede ser *
Access-Control-Expose-HeadersLista las cabeceras de respuesta que JS puede leerX-Request-Id, X-Total-CountPor defecto solo se puede leer Cache-Control/Content-Language/Content-Type/Expires/Last-Modified/Pragma; cabeceras personalizadas deben listarse aquí
Access-Control-Max-AgeTiempo de caché de resultado preflight (segundos)3600Límite Chrome/Firefox 86400 segundos (2 horas), Safari más corto; valor -1 deshabilita caché enviando preflight cada vez
VaryIndica a CDN/proxy qué cabeceras de solicitud afectan el contenido de respuestaOriginAl devolver Allow-Origin dinámicamente se debe añadir Vary: Origin, de lo contrario caché CDN causará problemas cross-origin intermitentes
Access-Control-Request-MethodCabecera de solicitud preflight, indica al servidor qué método usa la solicitud realPUTEnviada automáticamente por el navegador en solicitudes OPTIONS, no requiere configuración manual en frontend
Access-Control-Request-HeadersCabecera de solicitud preflight, indica al servidor qué cabeceras llevará la solicitud realcontent-type, x-tokenEnviada automáticamente por el navegador en solicitudes OPTIONS, múltiples cabeceras separadas por coma

Tabla de判定 de qué solicitudes disparan preflight

Condición de activación¿Dispara preflight?Explicación detallada
Método de solicitud es un método distinto de GET/HEAD/POST (PUT/DELETE/PATCH/CONNECT/OPTIONS/TRACE)Mientras el método no sea estos tres, aunque no haya cabeceras personalizadas definitivamente disparará preflight
Content-Type no es text/plain, multipart/form-data, application/x-www-form-urlencoded¡El más común application/json definitivamente dispara preflight! Mucha gente pisa esta trampa
La solicitud contiene cabeceras personalizadas (no en la lista segura CORS)Por ejemplo X-Token, X-Requested-With, Authorization, X-Custom-Header etc. todas disparan
Se usa ReadableStream como cuerpo de solicitud (subida de stream)Las APIs modernas de subida por stream del navegador disparan preflight
Se registra un escuchador de eventos en XMLHttpRequest.upload (escuchar progreso de subida)Mientras uses xhr.upload.onprogress disparará preflight
Método de solicitud es GET/HEAD/POST, Content-Type es uno de los tres tipos simples, sin cabeceras personalizadasNoEsto pertenece a solicitud simple, se envía directamente, no envía OPTIONS
fetch establece mode: 'no-cors'No envía preflight CORS pero la respuesta es opacaEn este modo JS no puede leer la respuesta, solo puede enviar solicitudes simples
fetch establece credentials: 'include' pero las demás condiciones son de solicitud simpleNo necesariamentecredentials por sí solo no dispara directamente preflight, solo si hay otras condiciones de activación simultáneas; atención que con credenciales la verificación CORS es más estricta
La misma URL ya tuvo preflight antes y está dentro del tiempo de caché Max-AgeNo (caché hit)Dentro del tiempo de caché no repetirá OPTIONS, a menos que se deshabilite caché o expire el caché

Tabla de referencia rápida de errores CORS comunes y soluciones

Mensaje de error en consolaCausa raízSolución
No 'Access-Control-Allow-Origin' header is presentLa respuesta no tiene cabeceras CORS en absoluto; el servidor no configuró CORS; gateway/proxy se comió las cabeceras; respuestas de error 500/404 no llevan cabeceraVerifica la configuración CORS del servidor; verifica que add_header de Nginx tenga añadido always; asegúrate que solicitudes OPTIONS devuelvan 2xx con cabeceras CORS
Valor de Allow-Origin 'XXX' no coincide con el Origin proporcionadoEl origen configurado en Allow-Origin no coincide con el Origin de la solicitud real; error de configuración (barra extra/puerto incorrecto/mezcla http/https)Verifica que la lista blanca de Origins incluya el origen de la solicitud; asegúrate que no haya barra final; www y sin www son orígenes diferentes; devuelve el Origin correcto dinámicamente
La respuesta preflight no pasó el control de acceso: el estado HTTP no es okLa solicitud OPTIONS devuelve estado no 2xx como 404/405/500; las rutas del servidor no manejan método OPTIONS; try_files de Nginx interceptó OPTIONSAsegúrate que solicitudes OPTIONS devuelvan 200/204; configura respuesta OPTIONS en servidor/gateway; verifica que la configuración de Nginx maneje OPTIONS correctamente
El método XXX no está permitido por Access-Control-Allow-MethodsAllow-Methods en la respuesta preflight no incluye el método HTTP que necesitas usar (como PUT no está listado)En Access-Control-Allow-Methods lista todos los métodos que necesites soportar, separados por coma
El campo de cabecera XXX no está permitido por Access-Control-Allow-HeadersEnviaste cabecera personalizada pero Allow-Headers no la declara; más común es olvidar Content-Type: application/json, AuthorizationEn Access-Control-Allow-Headers lista todas las cabeceras de solicitud utilizadas, incluyendo Content-Type (cuando es de tipo no simple)
Cuando el flag de credenciales es true, Allow-Origin no puede ser comodín *En solicitudes con credenciales (withCredentials=true) el servidor devolvió Allow-Origin: *, esto está explícitamente prohibido por la especificaciónNo se puede usar *, debe devolver el origen específico según el Origin de la solicitud; confirma también Allow-Credentials: true
Cuando el flag de credenciales es true, Allow-Headers no puede ser comodín *En solicitudes con credenciales Allow-Headers se estableció como *, algunos navegadores (como Chrome) no lo permitenLista todas las cabeceras de solicitud realmente utilizadas, no uses *
Redirección preflight rechazada (Preflight redirect is not allowed)La solicitud OPTIONS devolvió redirección 3xx, el navegador no sigue redirecciones de solicitudes preflightAsegúrate que la solicitud OPTIONS devuelva directamente 200, no redireccione; corrige la configuración del servidor para evitar saltos en OPTIONS