
El mensaje se lee más o menos así, y la mitad que importa es el final:
Access to fetch at 'https://api.example.com/orders' from origin
'https://app.example.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.
Léelo como una frase sobre quién se negó. El servidor respondió. Tu petición salió del navegador, llegó a la otra máquina y volvió con una respuesta. Después el navegador se negó a entregar esa respuesta a tu JavaScript, porque la respuesta no decía que tu origen tuviera permiso para leerla.
Ese único hecho descarta casi todo lo que se prueba primero. No hay nada roto en la red. El servidor no está caído. No hay ningún cortafuegos por medio. La respuesta está en el navegador, y el navegador no la va a pasar.
Por qué el navegador hace esto
Sin ello, cualquier página que visites podría hacer peticiones en silencio a tu banco, a tu correo web o a las herramientas internas de tu empresa usando las cookies que ya están en tu navegador, y leer las respuestas.
Así que la regla es: JavaScript puede enviar una petición a otro origen, pero solo puede leer la respuesta si el servidor que responde dice que ese origen tiene permiso. El permiso tiene que venir del servidor al que se pregunta, porque es la única parte que sabe quién debería estar leyendo eso.
Dos consecuencias que sorprenden:
- Solo se aplica al JavaScript del navegador. La misma petición desde curl, desde Postman o desde tu propio backend funciona, porque ninguno de ellos es un navegador custodiando la sesión de nadie. Una petición que funciona en Postman y falla en la página no es prueba de que el servidor esté roto.
- Desactivarlo en tu navegador no arregla nada. Un flag o una extensión que apaga la comprobación hace que el error desaparezca en una máquina mientras todos los visitantes lo siguen viendo. Es una manera de confirmar el diagnóstico, no un arreglo, y usar el navegador así a diario es una idea genuinamente mala.
Leer qué regla ha fallado
El final del mensaje nombra la regla, y hay tres con las que te vas a encontrar.
No ‘Access-Control-Allow-Origin’ header is present. (No hay ninguna cabecera ‘Access-Control-Allow-Origin’.) El caso simple. El servidor no envió nada sobre permisos, así que el navegador da por hecho que no hay ninguno.
Response to preflight request doesn’t pass access control check. (La respuesta a la petición de
preflight no pasa la comprobación de control de acceso.) El fallo ocurrió antes de que tu petición
llegara a enviarse. Cualquier cosa que vaya más allá de una petición simple, un PUT o un DELETE,
una cabecera propia como Authorization, un content type JSON, hace que el navegador envíe antes
una petición OPTIONS preguntando si la de verdad está permitida. Si ese OPTIONS devuelve un 404,
un 500, una redirección o un 200 sin las cabeceras correctas, la petición real no llega a ocurrir.
*The value of ‘Access-Control-Allow-Origin’ must not be the wildcard ‘’ when credentials mode is
‘include’.** (El valor no puede ser el comodín ‘*’ cuando se envían credenciales.) Estás enviando
cookies, y * no es lo bastante específico para eso. El servidor tiene que nombrar tu origen
exactamente y añadir Access-Control-Allow-Credentials: true.
Dónde va el arreglo
En el servidor que es dueño del recurso, en todos los casos. No en tu página, no en el navegador, no en un proxy que pongas delante de tu propio front end.
Si la API es tuya, eso significa devolver Access-Control-Allow-Origin con el origen que quieras
permitir y, para los preflights, responder a OPTIONS con los métodos y las cabeceras permitidos y
un estado 2xx. Si la API es de otro y no permite el acceso desde el navegador, entonces el acceso
desde el navegador no está en oferta: llámala desde tu propio backend y deja que tu página hable con
él.
Session Replay
Extensión gratuita de Chrome. Un clic en la página que se está portando mal captura la captura de pantalla, la consola y el registro de red, y te devuelve un enlace para pegar en el ticket.
En localhost, que es donde casi todo el mundo se lo encuentra
http://localhost:3000 y http://localhost:8080 son orígenes distintos, así que un front end en un
puerto llamando a una API en otro es una petición entre orígenes y se lleva el tratamiento completo.
La respuesta correcta en desarrollo es un proxy: tu servidor de desarrollo reenvía /api a la API,
de modo que el navegador ve un solo origen y la cuestión nunca se plantea. Toda herramienta de front
end moderna lo trae incorporado y cuesta una línea de configuración. La respuesta equivocada es
arrancar Chrome con la seguridad web desactivada, porque entonces el código solo funciona para quien
haya hecho lo mismo.
Lo que ve tu código
Nada útil, que es la parte que hace confuso depurar esto.
Una respuesta bloqueada no llega como un error que puedas inspeccionar. fetch() se rechaza con el
genérico
TypeError: Failed to fetch,
sin estado y sin cuerpo, porque dejar que la página lea por qué la bloquearon filtraría justo la
información que la regla existe para proteger.
Así que el mensaje de CORS de la consola no es un error que tu código haya capturado. Es el navegador diciéndote a ti, que programas, lo que ha hecho. Tu código no puede verlo, ni registrarlo, ni reportarlo.
Eso conviene saberlo cuando el aviso llega de otra persona. Un usuario que se topa con un fallo de
CORS ve una funcionalidad que en silencio no hace nada, tu seguimiento de errores anota un Failed
to fetch sin detalle, y la frase que lo habría explicado se imprimió en una consola que nadie
guardó.
Cómo encontrarlo en un informe
Dos sitios, y quieres los dos.
La consola lleva el mensaje de CORS con el origen, la URL y la regla que falló. El panel de
red muestra la petición en sí, y, en un fallo de preflight, la petición OPTIONS que está encima
de la de verdad, con su propio estado. Abre sus cabeceras de respuesta: lo que falta ahí es todo el
diagnóstico.
Si la petición no aparece en el panel de red en absoluto, el navegador se negó antes de enviarla, lo que apunta a contenido mixto o a una extensión y no a CORS.
La versión corta
- El servidor respondió; el navegador se negó a dejar que tu código leyera la respuesta.
- Solo se aplica al JavaScript del navegador, así que curl y Postman no prueban nada.
- El final del mensaje nombra qué regla ha fallado. Lee eso, no el principio.
- Los fallos de preflight ocurren antes de tu petición, en un
OPTIONSque el navegador envió por ti. - El arreglo va en el servidor que responde. Desactivar la comprobación en local es un diagnóstico, no un arreglo.
- En localhost, usa un proxy de desarrollo para que solo haya un origen.