CORS es un mecanismo del navegador que permite al servidor declarar qué otros origins pueden llamarlo, relajando a propósito la política de same-origin. Dos reencuadres disuelven la mayor parte de la confusión: quien lo aplica es el navegador (no el servidor — por eso curl funciona), y quien lo corrige es el servidor (no tu frontend — por eso ninguna cantidad de JavaScript ayuda). Todo lo demás son headers.
Puntos clave
| Pregunta | Respuesta |
|---|---|
| El fundamento | Política de same-origin: los scripts no leen respuestas de otros origins por defecto |
| Un origin | esquema + host + puerto — los tres deben coincidir |
| CORS | Headers del servidor haciendo opt-in de origins específicos; el navegador aplica |
| Preflight | Un OPTIONS “¿puedo?” antes de solicitudes no simples |
| La verdad eterna | Los errores de CORS se corrigen en el servidor, punto |
El protocolo entero, en el cable
# Preflight: el navegador pregunta antes de un PUT con header de autenticación
OPTIONS /classes/Product HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: x-parse-session-token
# El permiso por escrito del servidor
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com ← este origin puede leer
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: x-parse-session-token
Access-Control-Max-Age: 86400 ← cachea esta respuesta; sáltate el preflight de mañana
# Entonces, y solo entonces, vuela la solicitud real.
Cómo se ve desde el código de la aplicación — y la verdad de plataforma que las pestañas enseñan: CORS es un asunto del navegador, con el que las apps nativas nunca se topan:
// Browser JavaScript — Back4app JS SDK
// A cross-origin call that just works: the platform answers the
// preflight and sends the CORS headers, so the browser lets it through
Parse.initialize('APP_ID', 'JS_KEY');
Parse.serverURL = 'https://parseapi.back4app.com'; // different origin than your site
const products = await new Parse.Query('Product').find();
// No proxy hacks, no "disable CORS" — the server side is configured correctly. // Flutter / Dart — Back4app Flutter SDK
// Native apps have no same-origin policy — CORS is a browser concern.
// Flutter Web, however, DOES enforce it: same browser rules apply there.
final query = QueryBuilder<ParseObject>(ParseObject('Product'));
final response = await query.query();
// Works identically on mobile and web because the server sends CORS headers. // iOS / Swift — Back4app Swift SDK
// No browser, no same-origin policy: CORS never applies to native iOS.
// The same backend serves browsers (with CORS headers) and apps alike.
let query = Product.query()
query.find { result in
if case .success(let products) = result { render(products) }
} // Android / Kotlin — Back4app Android SDK
// No browser, no same-origin policy: CORS never applies to native Android.
// The same backend serves browsers (with CORS headers) and apps alike.
val query = ParseQuery.getQuery<ParseObject>("Product")
query.findInBackground { products, e ->
if (e == null) render(products)
} El flujo, decidido
Dos detalles cargan con la mayoría de las sesiones de depuración: una solicitud simple (GET/HEAD/POST con headers de la safelist y content types comunes) se salta el preflight — por eso agregar un header Authorization de repente “rompe” un endpoint que funcionaba; y las solicitudes con credenciales lo aprietan todo — las cookies solo fluyen con Access-Control-Allow-Credentials: true más un origin exacto, nunca el wildcard, según la especificación.
Cómo corregir errores de CORS (error → causa → corrección)
| La consola dice | Lo que realmente pasó | Corrección (siempre server-side) |
|---|---|---|
| No ‘Access-Control-Allow-Origin’ header | El servidor nunca hizo opt-in de tu origin | Agrega tu origin a la lista permitida |
| Origin not allowed by Access-Control-Allow-Origin | La allow-list existe; tú no estás en ella | Agrega el esquema+host+puerto exactos |
| Response to preflight… doesn’t pass | OPTIONS sin manejar o headers incompletos | Responde el OPTIONS con métodos/headers |
| Wildcard ’*’ cannot be used with credentials | Cookies + * — combinación prohibida | Lista los origins explícitamente |
| Request header not allowed | Header personalizado fuera de la allow-list | Agrégalo a Access-Control-Allow-Headers |
Y el anti-arreglo que merece nombre: deshabilitar con flags del navegador y los proxies permisivos de desarrollo vuelven el error invisible solo en tu máquina — el despliegue sigue roto para los usuarios. La corrección real es una configuración de servidor medida en minutos.
Casos de uso comunes
- SPA + API en origins distintos — app.example.com llamando a api.example.com: el caso cotidiano para el que existe CORS.
- Desarrollo local — localhost:3000 contra un backend real; permite el origin de desarrollo, no apagues el escudo.
- APIs públicas — origin wildcard, sin credenciales: correcto y seguro para datos genuinamente públicos.
- Plataformas multi-frontend — varias apps, un backend, una lista explícita de origins por entorno.
- Backends BaaS — la plataforma responde los preflights y envía los headers; tu trabajo se reduce a declarar los origins permitidos.
Wildcard vs. origins explícitos: matriz de decisión
| Configuración | Correcta cuando… | Nunca cuando… |
|---|---|---|
Wildcard * | API pública, sin credenciales | Existen cookies o sesiones de usuario |
| Lista explícita de origins | APIs privadas, apps con credenciales | — (esta es la respuesta por defecto) |
| Reflejar el origin de la solicitud | Casi nunca | Combinada con credenciales — el agujero clásico |
| Listas por entorno | Higiene de dev/staging/prod | Producción heredando las entradas localhost de dev |
Un reencuadre de seguridad cierra la matriz: CORS no es control de acceso — las apps nativas y los servidores lo ignoran por completo, así que protege a los usuarios del navegador de sitios maliciosos, no a tu API de sus llamadores. La autenticación y los permisos en la capa de datos hacen ese trabajo; CORS solo decide qué scripts de qué sitios pueden leer las respuestas.
Limitaciones y trade-offs
- Solo gobierna navegadores. Cualquier cosa que no sea un navegador pasa de largo — nunca confundas una lista de origins con autorización.
- Los preflights cuestan un round trip en solicitudes no simples; el cache vía
Access-Control-Max-Agees la corrección barata y olvidada. - La mala configuración falla cerrada y ruidosa — bueno para la seguridad, brutal para depurar sin la tabla error→corrección de arriba.
- Las listas de origins son estado de entorno. Los origins de staging se cuelan en las configuraciones de producción; audita la lista como cualquier credencial.
- CORS ≠ CSRF ≠ CSP. Siglas vecinas, defensas separadas — las cookies aún necesitan protección SameSite/token, las páginas aún necesitan una content policy, y CORS se encarga solo de la lectura cross-origin.
CORS en Back4app
Back4app es una plataforma open-source de Backend as a Service (BaaS) que combina base de datos gestionada, APIs REST y GraphQL generadas automáticamente, autenticación, almacenamiento de archivos y funciones serverless con Cloud Code. La tarea de CORS llega resuelta: las APIs de la plataforma responden los preflights y envían los headers correctos, así que las apps de navegador llaman al backend desde los origins permitidos sin hacks de proxy — la pestaña de JavaScript de arriba es la experiencia entera — mientras las pestañas de Flutter, Swift y Kotlin demuestran la verdad más silenciosa: los clientes nativos nunca se topan con la ceremonia. La seguridad real se queda donde pertenece: sesiones, ACLs y permisos a nivel de clase aplicados server-side en cada solicitud, venga del origin que venga.
Preguntas frecuentes
¿Qué es CORS en términos simples?
El sistema de permisos del navegador para llamadas de API entre sitios. Por defecto, los scripts de un origin no pueden leer respuestas de otro — la política de same-origin. CORS es cómo un servidor hace el opt-in: headers de respuesta que declaran qué origins, métodos y headers acepta. El navegador aplica; el servidor declara; tu código de frontend es solo el mensajero.
¿Qué es un origin, exactamente?
La tripleta de esquema, host y puerto. https://app.example.com y https://api.example.com son origins distintos (difiere el host); también las versiones http y https de un mismo sitio (difiere el esquema), y :3000 vs :8080 en desarrollo (difiere el puerto). Toda decisión de CORS compara esas tres partes — nada más de la URL importa.
¿Cómo resolver un error de CORS?
El navegador llamó a un origin distinto y la respuesta no traía un header Access-Control-Allow-Origin que coincidiera con el tuyo — así que el navegador impidió que tu script la leyera. La solicitud muchas veces llegó bien al servidor; el bloqueo es client-side, en la lectura. Por eso la corrección es siempre configuración del servidor — agregar tu origin a la lista permitida — y nunca código de frontend.
¿Qué es una solicitud de preflight?
La verificación previa de permiso del navegador para solicitudes no simples: antes de enviar un PUT, un DELETE o cualquier cosa con headers personalizados, como un token de autorización, envía una solicitud OPTIONS preguntando "¿puedo?". Los headers del servidor responden qué métodos, headers y origins están permitidos; solo entonces vuela la solicitud real. Los preflights son cacheables vía Access-Control-Max-Age.
¿Por qué mi solicitud funciona en curl pero falla en el navegador?
Porque CORS es aplicación por parte del navegador, no rechazo por parte del servidor. Herramientas como curl y las apps móviles nativas no tienen política de same-origin, así que leen la respuesta sin problema. El navegador aplica la política en nombre de su usuario — la diferencia que estás viendo es el punto de aplicación, y es también la prueba de que CORS no es control de acceso.
¿Access-Control-Allow-Origin: * es seguro?
Para APIs genuinamente públicas y sin credenciales, sí. El wildcard está prohibido en modo con credenciales — los navegadores rechazan cookies contra él por diseño — y reflejar origins arbitrarios mientras se permiten credenciales es la mala configuración clásica que convierte a CORS de escudo en agujero. Las APIs privadas listan origins explícitamente.
¿Puedo simplemente deshabilitar CORS para arreglar el error?
Solo en el sentido en que quitar la alarma de humo arregla el incendio. Las flags del navegador y los proxies permisivos enmascaran el síntoma en tu máquina mientras le entregan la falla a cada usuario. La corrección correcta toma minutos: configura el servidor (o el dashboard de la plataforma) para permitir los origins que deben llamarlo.
¿Cuál es la diferencia entre CORS, CSRF y CSP?
Tres trabajos distintos. CORS gobierna qué origins pueden leer respuestas de un servidor. CSRF es un ataque — montarse en las cookies de un usuario para forjar solicitudes — contrarrestado con tokens y cookies SameSite, no con CORS. CSP es la política de la propia página restringiendo qué puede cargar y ejecutar, la herramienta anti-XSS. Siglas vecinas, mecanismos ortogonales.