CORS: qué es y por qué el navegador bloquea una API | POLPROG Ir al contenido

CORS: qué es y por qué el navegador bloquea una API

CORS no es un mecanismo que simplemente bloquee conexiones a una API. Es un protocolo del navegador construido sobre la same-origin policy que decide si JavaScript de un origin puede leer una respuesta de otro origin. En algunos casos la petición HTTP se envía y el navegador solo bloquea el acceso del script a la respuesta. En otros casos se envía primero un preflight OPTIONS y su resultado decide si la petición real puede enviarse.

Publicado Escrito por Tiempo de lectura 19 min de lectura

CORS no es un mecanismo que simplemente bloquee conexiones a una API. Es un protocolo del navegador construido sobre la same-origin policy que decide si JavaScript de un origin puede leer una respuesta de otro origin. En algunos casos la petición HTTP se envía y el navegador solo bloquea el acceso del script a la respuesta. En otros casos se envía primero un preflight OPTIONS y su resultado decide si la petición real puede enviarse.

En esta página
  1. 1CORS empieza con la same-origin policy
  2. 2Qué es exactamente un origin
  3. 3Qué significa realmente que el navegador bloquea la API
  4. 4Peticiones simples: cuándo se evita el preflight
  5. 5Preflight OPTIONS: qué comprueba el navegador
  6. 6Los headers CORS más importantes
  7. 7`Access-Control-Allow-Origin`: wildcard u origin explícito
  8. 8Cookies y credentials: fuente frecuente de errores
  9. 9Por qué `mode: "no-cors"` normalmente no arregla nada
  10. 10Por qué curl, Postman o un backend pueden funcionar
  11. 11CORS no sustituye CSRF ni autorización
  12. 12Errores frecuentes y diagnóstico
  13. 13Configuraciones CORS peligrosas
  14. 14Rendimiento: coste del preflight y `Access-Control-Max-Age`
  15. 15Configuración práctica y checklist de despliegue

CORS empieza con la same-origin policy

La same-origin policy es un mecanismo de seguridad fundamental del navegador. Limita la capacidad de un documento o script para leer datos de otro origin y ayuda a impedir que un sitio malicioso lea información de un servicio donde el usuario ya está autenticado. [3]

CORS, Cross-Origin Resource Sharing, es un mecanismo HTTP que permite al servidor relajar esa regla y declarar explícitamente qué origins pueden leer una respuesta. Fetch Standard lo describe como un protocolo opt-in. [1][2]

Qué es exactamente un origin

PáginaURL objetivoRelaciónPor qué
https://app.example.comhttps://app.example.com/apiMismo originMismo esquema, host y puerto
https://app.example.comhttps://api.example.comOrigin distintoHost distinto
https://app.example.comhttp://app.example.comOrigin distintoEsquema distinto
https://app.example.comhttps://app.example.com:8443Origin distintoPuerto distinto
https://app.example.comhttps://app.example.com/v2Mismo originSolo cambia la ruta

Un origin se define por esquema, host y puerto. Dos URL son same-origin solo si coinciden los tres. La ruta no importa. [3][4]

Por tanto, `https://app.example.com` y `https://api.example.com` son origins distintos. También lo son HTTP frente a HTTPS o dos puertos distintos. [4]

Same-origin y same-site no son conceptos equivalentes. Es especialmente importante con cookies, porque `SameSite` usa el concepto de site, mientras CORS usa origin. [13]

Qué significa realmente que el navegador bloquea la API

Decir que el navegador bloquea la petición suele ser demasiado simple. Para una petición cross-origin simple, puede enviarla, recibir una respuesta HTTP válida y después negarse a exponerla a JavaScript si falla la comprobación CORS. [1][6]

Para peticiones con preflight, el flujo cambia. El navegador envía primero `OPTIONS`. Si falla el preflight, la petición real no se envía. [2][5][6]

Esta diferencia importa en operaciones que modifican datos. Un error CORS no implica automáticamente que el backend no haya recibido ninguna petición.

Peticiones simples: cuándo se evita el preflight

MétodoHeaders de ejemploPetición simpleEfecto
GETAcceptSin preflight
POSTContent-Type: text/plainSin preflight
POSTContent-Type: application/jsonNoJSON provoca preflight
PUTContent-Type: application/jsonNoEl método provoca preflight
GETAuthorization: Bearer ...NoEl header provoca preflight

Una petición solo es simple si cumple las condiciones de la CORS safelist. Los métodos permitidos son `GET`, `HEAD` y `POST`. Los headers establecidos manualmente deben estar en la lista permitida y `Content-Type` solo puede usar `application/x-www-form-urlencoded`, `multipart/form-data` o `text/plain`. [1]

Un `POST` típico con `Content-Type: application/json` no es simple y suele provocar preflight. Lo mismo ocurre con `PUT`, `DELETE` o headers personalizados como `X-Request-ID`. [1][11]

Preflight OPTIONS: qué comprueba el navegador

El preflight es una petición `OPTIONS` automática. El navegador envía `Origin`, `Access-Control-Request-Method` y, si hace falta, `Access-Control-Request-Headers`. El servidor responde con origins, métodos y headers permitidos. [2][5]

Si la aplicación quiere enviar `DELETE` con `Authorization`, el navegador puede comprobar primero si ese origin puede usar ese método y ese header. Solo una respuesta positiva permite continuar. [5]

El preflight añade un round trip adicional, aunque el resultado puede guardarse en una caché CORS específica separada de la caché HTTP normal. [5][10]

Los headers CORS más importantes

HeaderDirecciónFunción
OriginPeticiónOrigin que inicia la petición
Access-Control-Allow-OriginRespuestaOrigin cuyo código puede recibir la respuesta
Access-Control-Allow-MethodsRespuesta preflightMétodos permitidos por preflight
Access-Control-Allow-HeadersRespuesta preflightHeaders de petición permitidos por preflight
Access-Control-Allow-CredentialsRespuestaPermiso para exponer respuesta con credentials
Access-Control-Expose-HeadersRespuestaHeaders adicionales de respuesta visibles para JavaScript
Access-Control-Max-AgeRespuesta preflightTiempo de caché del resultado preflight
Vary: OriginRespuestaIndica a la caché que la respuesta varía según Origin

`Origin` identifica el origin que inicia la petición. `Access-Control-Allow-Origin` es un header de respuesta que indica si el navegador puede compartir la respuesta con código de ese origin. [1][2][8]

`Access-Control-Allow-Methods` y `Access-Control-Allow-Headers` son especialmente relevantes para preflight. `Access-Control-Allow-Credentials` permite exponer respuestas con credentials y `Access-Control-Expose-Headers` puede mostrar headers adicionales a JavaScript. [1][2]

Si el servidor devuelve dinámicamente `Access-Control-Allow-Origin`, MDN recomienda también `Vary: Origin`. [1]

`Access-Control-Allow-Origin`: wildcard u origin explícito

`Access-Control-Allow-Origin: *` permite compartir una respuesta con cualquier origin para peticiones sin credentials. Puede ser correcto para una API realmente pública. [2][8]

Para endpoints privados o sensibles, el conjunto permitido debe ser mínimo. MDN y OWASP recomiendan origins específicos cuando no se necesita acceso global. [14][17]

Si se permiten varios origins, el servidor debe comparar el `Origin` entrante con una allowlist y devolver solo el valor aceptado. No debe reflejar cualquier origin sin validación. [8][16]

Cookies y credentials: fuente frecuente de errores

Fetch puede usar `credentials: "include"` para incluir credentials en peticiones cross-origin. Eso no basta. El servidor debe responder con `Access-Control-Allow-Credentials: true` y un `Access-Control-Allow-Origin` explícito. `*` no está permitido en este caso. [2][6][9]

Las cookies también siguen sus propias reglas. `SameSite` puede impedir que una cookie se envíe en una petición cross-site y `SameSite=None` requiere `Secure`. [13]

CORS y `SameSite` resuelven problemas diferentes. CORS puede ser correcto y faltar la cookie, o la cookie puede enviarse y CORS seguir bloqueando el acceso de JavaScript a la respuesta.

Por qué `mode: "no-cors"` normalmente no arregla nada

`fetch(..., { mode: "no-cors" })` no es una forma de saltarse CORS cuando la aplicación necesita leer la respuesta. Métodos y headers quedan restringidos y la respuesta es opaque. JavaScript no puede leer body ni headers y el status expuesto es `0`. [6][7][11]

`no-cors` tiene usos especializados, incluidos algunos escenarios con Service Worker, pero suele ser una mala solución para una API JSON normal. [6]

Si no controlas la API externa y no ofrece CORS, un backend o proxy propio puede hacer la petición servidor a servidor. [11][12]

Por qué curl, Postman o un backend pueden funcionar

La same-origin policy y CORS son mecanismos aplicados por el navegador. Un cliente HTTP fuera de ese modelo no está sujeto a la misma comprobación. Por eso un endpoint puede funcionar con curl o una herramienta API y fallar desde JavaScript de una página. [2][3][16]

Esto demuestra también que CORS no es autenticación de API. Un cliente fuera del navegador puede elegir su propio `Origin`, por lo que OWASP desaconseja usarlo como prueba de identidad. [16]

La API sigue necesitando autorización, tokens, sesiones, controles de permisos y validación en servidor.

CORS no sustituye CSRF ni autorización

La same-origin policy limita principalmente lecturas cross-origin. Formularios y algunas peticiones simples pueden enviarse entre sitios, por lo que las aplicaciones basadas en cookies siguen necesitando defensa CSRF. [3][15]

OWASP recomienda expresamente no usar solo CORS u `Origin` como control de acceso a recursos sensibles. Autenticación y autorización siguen siendo necesarias. [16]

CORS responde si el navegador puede exponer una respuesta a código de un origin, no si el usuario o cliente tiene permiso para ejecutar la operación.

Errores frecuentes y diagnóstico

La ausencia de `Access-Control-Allow-Origin` es un error habitual. También son frecuentes un método no permitido, headers ausentes en `Access-Control-Allow-Headers`, `*` con credentials, una mala respuesta OPTIONS o redirects dentro del flujo CORS. [11][12]

JavaScript recibe deliberadamente pocos detalles. La consola y Network de DevTools ofrecen el diagnóstico más útil. [1][11]

`CORS request did not succeed` también puede deberse a DNS, timeout, conexión rechazada, TLS, mixed content o extensiones del navegador. [11]

  • Comprueba el origin exacto del frontend: esquema, host y puerto.
  • Inspecciona `OPTIONS` cuando exista.
  • Revisa status y headers `Access-Control-Allow-*` del preflight.
  • Comprueba que la respuesta real también incluya los headers CORS necesarios.
  • Con cookies revisa `credentials`, `Access-Control-Allow-Credentials`, `SameSite` y `Secure`.
  • Revisa redirects, TLS, mixed content y errores de red.
  • Compara la petición del navegador con una petición funcional de curl o una herramienta API.

Configuraciones CORS peligrosas

Reflejar ciegamente el `Origin` recibido es peligroso cuando peticiones con credentials devuelven datos sensibles. Un atacante puede controlar un origin e intentar leer respuestas en el contexto de un usuario autenticado. [16]

Expresiones regulares demasiado amplias y confiar en todos los subdominios también puede ser arriesgado si alguno puede ser tomado o controlado por otra parte. OWASP recomienda una allowlist con coincidencia exacta. [16][17]

`Access-Control-Allow-Origin: *` no es automáticamente una vulnerabilidad para datos totalmente públicos sin credentials. El problema es una política más permisiva de lo necesario. [8][17]

Rendimiento: coste del preflight y `Access-Control-Max-Age`

El preflight añade una petición `OPTIONS` antes de la real y puede aumentar latencia. Su resultado puede cachearse con `Access-Control-Max-Age` en una caché separada de la HTTP normal. [5][10]

MDN indica que el valor por defecto sin header es 5 segundos. Los navegadores aplican límites propios. Firefox limita a 86400 segundos y Chromium desde la versión 76 a 7200 segundos. [10]

Conviene cachear políticas estables, pero no tanto como para dificultar retirar rápidamente una política accidentalmente demasiado amplia.

Configuración práctica y checklist de despliegue

Primero decide si el endpoint debe ser accesible cross-origin. Si no, no añadas headers CORS. Si sí, define allowlist de origins, métodos, headers y si se necesitan credentials. [14][17]

Con una allowlist dinámica, devuelve solo un origin validado y añade `Vary: Origin`. Con credentials, devuelve origin explícito y `Access-Control-Allow-Credentials: true`. El frontend no puede sustituir esta decisión del servidor. [1][2]

Después del despliegue prueba una petición simple, un preflight, un escenario con cookie, un origin no permitido y errores reales en DevTools.

  • ¿El acceso cross-origin es realmente necesario?
  • ¿Qué origins exactos deben permitirse?
  • ¿Se necesitan credentials?
  • ¿Qué métodos y headers de petición son necesarios?
  • ¿`OPTIONS` llega a la aplicación y responde correctamente?
  • ¿La respuesta real contiene los headers CORS necesarios?
  • ¿Las respuestas con origin dinámico incluyen `Vary: Origin`?
  • ¿El endpoint sigue exigiendo autenticación y autorización normales?
  • ¿Se ha probado la política con un origin permitido y otro no permitido?

CORS debe entenderse como una regla del navegador sobre acceso a respuestas, no como una frontera universal de seguridad de una API. Primero identifica los origins de frontend y API, después determina si la petición es simple o requiere preflight y finalmente revisa OPTIONS y los headers CORS de la respuesta real. Una configuración segura permite únicamente los origins, métodos y headers necesarios, mientras autenticación y autorización siguen en la aplicación.

CORS Same-Origin Policy Web Security HTTP API Fetch API Preflight Cookies CSRF Frontend

Preguntas frecuentes

¿Qué es CORS?

CORS es un protocolo HTTP usado por navegadores para controlar el acceso a respuestas entre origins distintos. El servidor declara mediante headers qué origins pueden leer una respuesta. [1][2]

¿Por qué la API funciona en Postman pero no en el navegador?

Porque CORS se aplica en el navegador como parte de la same-origin policy. Los clientes HTTP fuera de ese modelo no están sujetos a la misma comprobación. [2][3][16]

¿CORS siempre bloquea el envío de la petición?

No. Una petición simple puede enviarse y solo bloquearse el acceso de JavaScript a la respuesta. Si falla un preflight obligatorio, la petición real no se envía. [1][5][6]

¿Qué provoca un preflight?

Entre otros casos, métodos fuera de GET, HEAD, POST, headers personalizados o Content-Type: application/json. El navegador envía primero OPTIONS. [1][5]

¿Debe añadirse Access-Control-Allow-Origin en frontend?

No. Es un header de respuesta del servidor. El frontend no puede concederse permiso a sí mismo. [1][8][12]

¿mode: "no-cors" arregla CORS?

No para una API normal cuyos datos deban leerse. La respuesta se vuelve opaque y body, headers y status normal no son accesibles. [6][7]

¿Se puede usar Access-Control-Allow-Origin: *?

Sí para respuestas públicas sin credentials. No para exponer respuestas con credentials. [2][8][9]

¿Cómo funciona CORS con cookies?

Frontend suele usar credentials: "include" y el servidor debe devolver un origin explícito y Access-Control-Allow-Credentials: true. La cookie sigue sujeta a SameSite y Secure. [6][9][13]

¿CORS protege una API frente a curl o bots?

No. CORS limita el acceso JavaScript del navegador. Un cliente externo puede enviar su propia petición y Origin, por lo que la API necesita autorización. [16]

¿CORS protege frente a CSRF?

No completamente. Las aplicaciones con cookies siguen necesitando protección CSRF. [3][15][16]

¿Para qué sirve Vary: Origin?

Si Access-Control-Allow-Origin cambia dinámicamente, Vary: Origin informa a la caché de que la respuesta depende del origin solicitante. [1]

¿Cómo reducir preflights?

Puede cachearse el resultado con Access-Control-Max-Age o diseñar peticiones simples cuando sea funcionalmente y en seguridad apropiado. [1][10][11]

Fuentes y referencias

  1. MDN, Cross-Origin Resource Sharing (CORS)123456789101112131415
  2. WHATWG, Fetch Standard, CORS protocol123456789101112
  3. MDN, Same-origin policy123456
  4. MDN, Origin12
  5. MDN, Preflight request1234567
  6. MDN, Using the Fetch API12345678
  7. MDN, RequestInit12
  8. MDN, Access-Control-Allow-Origin123456
  9. MDN, Access-Control-Allow-Credentials123
  10. MDN, Access-Control-Max-Age1234
  11. MDN, CORS errors1234567
  12. MDN, CORS header Access-Control-Allow-Origin missing123
  13. MDN, Set-Cookie123
  14. MDN, Practical CORS configuration12
  15. MDN, Cross-site request forgery (CSRF)12
  16. OWASP, HTML5 Security Cheat Sheet, Cross Origin Resource Sharing123456789
  17. OWASP, HTTP Headers Cheat Sheet, Access-Control-Allow-Origin1234

¿Te ha resultado útil?

Recibe nuevos artículos por email

Un correo breve por cada nuevo artículo de la base de conocimiento. Sin spam, te das de baja con un clic.

Solo usamos tu email para enviar nuevos artículos. Sin compartir con terceros.

Volver a la base de conocimiento