El ticket de soporte llega con una captura de pantalla: "CORS está bloqueando nuestra API". El desarrollador frontend insiste en que el backend "abra CORS como *". Al mismo tiempo, curl sin encabezado Origin funciona perfectamente. La confusión cuesta días porque tratamos a CORS como un bloqueo del servidor, cuando es una política del navegador la que controla quién puede leer la respuesta de JavaScript.
CORS no protege su API de ataques directos: curl, Postman, scripts de servidor y aplicaciones móviles nativas ignoran CORS. La autenticación (JWT, sesión, mTLS) sigue siendo esencial. CORS responde una pregunta específica: ¿https://app.example.com tiene derecho a exponer la respuesta JSON a su JavaScript?
Los incidentes recurrentes provienen de la verificación previa de OPCIONES que devuelve 401, el * incompatible con las cookies o encabezados CORS duplicados entre nginx y el marco. Aquí se explica cómo estructurar una política clara sin abrir la puerta a toda la web.
OPCIONES y métodos de verificación previa
Las solicitudes “no simples” (JSON POST, encabezados personalizados, PUT/DELETE) activan una verificación previa OPCIONES. El navegador envía Origin, Access-Control-Request-Method, a veces Access-Control-Request-Headers. El servidor debe responder 204/200 con encabezados CORS antes de la solicitud real.
Error clásico: el middleware de autenticación trata las OPCIONES como POST y devuelve 401. El navegador muestra un error CORS opaco: el desarrollador culpa a CORS, la causa es la autenticación.
Excluya OPCIONES del control de autenticación en rutas API públicas. Prueba con:
curl -X OPCIONES -H "Origen: https://app.example.com" \
-H "Método-de-solicitud-de-control-de-acceso: POST" \
-yo https://api.example.com/v1/resource
Orígenes, credenciales y Vary
| Escenario | Control-de-acceso-permitir-origen | Credenciales |
|---|---|---|
| API pública sin cookies | origen exacto o lista | falso |
| SPA con sesión de cookies | origen exacto en la lista blanca | verdadero + Permitir credenciales |
* con galletas | prohibido por especificación | fallo del navegador |
Con Access-Control-Allow-Credentials: true, devuelve el origen exacto de la solicitud si está en tu lista blanca, nunca *.
Agregue Vary: Origin en las respuestas CORS dinámicas para evitar que una CDN proporcione el origen incorrecto en la caché.
Sólo una capa emite los encabezados
nginx y Express (o Laravel, Django), que agregan Access-Control-Allow-Origin, producen encabezados duplicados; algunos navegadores fallan de manera intermitente, el peor tipo de error de producción.
Elija: CORS en el borde (nginx/API gateway) o en la aplicación, no ambos. Documento en la arquitectura ADR.
Auditoría anual: grep config nginx + código middleware: una única fuente de verdad.
CORS ≠ Seguridad API
Un atacante falsifica una solicitud desde su servidor sin el navegador Origin; CORS no se aplica. La limitación de velocidad, la autenticación, la entrada de validación y WAF siguen siendo necesarios.
Breve documento interno: “Las aplicaciones móviles nativas no necesitan CORS”: reduce los tickets de falsos positivos en el backend.
OpenAPI: enumera los orígenes autorizados por entorno y ejemplo de copiar y pegar de verificación previa de curl para socios.
Pruebas y observabilidad
Colección Partner Postman mantenida en CI: la regresión CORS se detectó antes de la fusión.
Métricas: relación de OPCIONES 204 frente a 401, orígenes desconocidos rechazados (registro agregado sin PII).
En caso de incidente: primero reproduzca en curl preflight, luego en el navegador con el caché deshabilitado: evita pistas falsas en la extensión de Chrome.
Revisión de la puerta de enlace
Encabezados CORS de una sola capa de auditoría anual: la aplicación nginx plus duplicada interrumpe los navegadores.
OPCIONES deberían responder rápidamente: el tiempo de espera parece CORS pero el servidor es lento.
Las aplicaciones móviles nativas no necesitan CORS: documento interno.
Monitoreo operativo
CORS OpenAPI documentado reduce los tickets de soporte de los socios: una inversión rentable. Ejemplo de copiar y pegar de verificación previa de curl por entorno. CI mantenida por la colección Partner Postman: la regresión CORS se detectó antes de la fusión. Documente las brechas entre la promesa del proveedor de hosting y la medición de campo en la revisión trimestral.
Continuación trimestral
CORS OpenAPI documentado reduce los tickets de soporte de los socios: una inversión rentable. Ejemplo de copiar y pegar de verificación previa de curl por entorno. CI mantenida por la colección Partner Postman: la regresión CORS se detectó antes de la fusión. Documente las brechas entre la promesa del proveedor de hosting y la medición de campo en la revisión trimestral.
Decide y avanza sin puntos ciegos
- Reproducir curl de verificación previa: OPCIONES con Origen y Método de solicitud de control de acceso antes de acceder a nginx.
- Orígenes explícitos en la lista blanca: nunca refleje arbitrariedad;
Variar: Origensi CDN. - Omitir autenticación en OPCIONES: middleware dedicado; 401 en verificación previa = boleto clásico.
- Kit de integración de socios: origen exacto, credenciales, ejemplo de recuperación, navegador 401 frente a CORS.
- Autenticación de API independiente: JWT, límite de velocidad, CSRF si son cookies; CORS no es el bloqueo del servidor.
API entre dominios: comparador, directorio, blog backend.
Preguntas frecuentes
¿Cors protege la API de ataques?
No: informa al navegador qué orígenes pueden leer la respuesta. curl, los scripts nativos móviles y de servidor ignoran CORS: la autenticación API sigue siendo esencial.
¿Por qué falla * con cookies?
La especificación no permite Access-Control-Allow-Origin: * cuando Access-Control-Allow-Credentials: true. Debe devolver el origen exacto de una lista blanca.
¿Qué hacer cuando OPCIONES devuelve 401?
El middleware de autenticación bloquea la verificación previa. Excluir OPCIONES del control de autenticación: causa común de tickets de "CORS rotos".
¿Cómo realizar pruebas sin el navegador?
Reproducir con curl -X OPCIONES -H Origen:https://app.example.com -H 'Access-Control-Request-Method: POST' en la URL de API.
Documente los orígenes autorizados en la incorporación de socios: menos despliegues de emergencia el viernes por la noche.
