Tabla de Contenidos
¿Qué es CORS y por qué existe?
Analogía práctica: Imagina que CORS es como el sistema de invitaciones de un evento. Tu frontend (el invitado) quiere acceder a recursos de una API (el evento). El navegador (el guardia) verifica si la API envió una invitación válida (headers CORS). Sin ella, no hay acceso.
CORS (Cross-Origin Resource Sharing) es un mecanismo de seguridad implementado por todos los navegadores modernos que permite a los servidores web declarar explícitamente qué orígenes externos pueden acceder a sus recursos.
¿Qué constituye un "origen"?
Un origen se define por la combinación única de tres componentes:
| Componente | Ejemplo | ¿Cambia el origen? |
|---|---|---|
| Protocolo (Scheme) | http:// vs https:// |
✅ Sí |
| Dominio (Host) | api.dominio.com vs app.dominio.com |
✅ Sí |
| Puerto | :3000 vs :8080 |
✅ Sí |
Ejemplos de diferentes orígenes:
// MISMO ORIGEN
https://app.midominio.com:443 → https://app.midominio.com:443
// ORÍGENES DIFERENTES
https://app.midominio.com → https://api.midominio.com // Host diferente
https://app.midominio.com → http://app.midominio.com // Protocolo diferente
https://app.midominio.com → https://app.midominio.com:8080 // Puerto diferente
https://app.midominio.com → https://midominio.com // Subdominio diferente
El "error CORS" no es un error del servidor
Insight crucial: Cuando ves un error CORS en la consola del navegador, tu servidor ya respondió (a veces incluso con 200 OK). El navegador simplemente está bloqueando que tu JavaScript acceda a esa respuesta por razones de seguridad.
Flujo típico de un request con CORS:
- Frontend: Tu aplicación en
https://app.comhace fetch ahttps://api.com/data - Navegador: Detecta origen cruzado y añade header
Origin: https://app.com - Servidor: Responde con los datos pero sin
Access-Control-Allow-Origin: https://app.com - Navegador: Bloquea el acceso a la respuesta → Error CORS en consola
¿Por qué en Postman/curl funciona pero en el navegador no?
Herramientas como Postman, curl o scripts de servidor no implementan políticas de mismo origen. Solo los navegadores web tienen esta restricción por seguridad.
Causas comunes de errores CORS y diagnóstico
1. Header Access-Control-Allow-Origin faltante o incorrecto
El servidor debe incluir este header con el origen exacto del frontend o * (con limitaciones).
Configuración correcta:
// Para un origen específico
Access-Control-Allow-Origin: https://app.midominio.com
// Para múltiples orígenes (requiere lógica en backend)
// El servidor debe verificar el Origin y responder con el valor apropiado
2. Fallo en preflight request (OPTIONS)
El navegador envía una solicitud OPTIONS antes de requests "complejos":
Request OPTIONS típico:
OPTIONS /api/users HTTP/1.1
Host: api.midominio.com
Origin: https://app.midominio.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Authorization, Content-Type
Respuesta OPTIONS esperada:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.midominio.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400 // Cache por 24 horas
3. Credenciales (cookies, autenticación) mal configuradas
Cuando envías cookies o usas credentials: 'include' en fetch:
Configuración incorrecta:
// Frontend
fetch('https://api.com/data', {
credentials: 'include' // Envía cookies
});
// Backend (ERROR - no compatible con credentials)
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true // Contradicción
Configuración correcta:
// Backend
Access-Control-Allow-Origin: https://app.midominio.com // Específico, NO *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: X-Auth-Token // Headers personalizados visibles
4. Headers personalizados no permitidos
Headers como X-API-Key, X-Custom-Header deben ser explícitamente permitidos:
Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key, X-Custom-Header
Soluciones prácticas por entorno y tecnología
Express.js (Node.js)
const express = require('express');
const cors = require('cors');
const app = express();
// Configuración básica para desarrollo
app.use(cors({
origin: 'http://localhost:3000',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
}));
// Configuración para producción (allowlist)
const allowedOrigins = [
'https://app.midominio.com',
'https://staging.midominio.com'
];
app.use(cors({
origin: function(origin, callback) {
// Permitir requests sin Origin (como mobile apps o curl)
if (!origin) return callback(null, true);
if (allowedOrigins.indexOf(origin) === -1) {
const msg = `Origen ${origin} no permitido por CORS`;
return callback(new Error(msg), false);
}
return callback(null, true);
},
credentials: true
}));
Django (Python)
# settings.py
INSTALLED_APPS = [
...
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # Debe ir antes de CommonMiddleware
...
]
# Configuración CORS
CORS_ALLOWED_ORIGINS = [
"https://app.midominio.com",
"https://staging.midominio.com",
"http://localhost:3000",
]
# Para desarrollo local
CORS_ALLOW_ALL_ORIGINS = True # Solo en desarrollo
# Permitir cookies
CORS_ALLOW_CREDENTIALS = True
# Headers permitidos
CORS_ALLOW_HEADERS = [
'content-type',
'authorization',
'x-csrftoken',
'x-requested-with',
]
Configuración en Nginx (proxy inverso)
server {
listen 80;
server_name api.midominio.com;
location / {
# Configuración CORS
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://app.midominio.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
# Para requests normales
add_header 'Access-Control-Allow-Origin' 'https://app.midominio.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
# Proxy a tu aplicación
proxy_pass http://localhost:8080;
}
}
Solución para desarrollo: Proxy en Vite/Webpack
// vite.config.js (Vite)
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
// Frontend llama a /api/users -> Vite redirige a localhost:3001/users
// Mismo origen = sin problemas CORS
CORS y Seguridad: Lo que debes saber
Advertencia importante: CORS NO es un mecanismo de autenticación/autorización. Solo controla qué frontends pueden acceder a tus recursos desde un navegador.
Mitos peligrosos sobre CORS:
Mito: "Si configuro CORS, mi API está segura"
CORS solo afecta a navegadores. Cualquiera puede hacer requests directamente a tu API con curl, Postman o scripts.
Realidad: La seguridad requiere múltiples capas
- Autenticación: Tokens JWT, OAuth, sessions
- Autorización: RBAC, permisos por recurso
- Validación: Input sanitization, schema validation
- Rate limiting: Protección contra abuso
- CORS: Solo una capa más
Patrones de seguridad recomendados:
API Keys para aplicaciones nativas/móviles
Para clientes que no son navegadores, usa API keys con rate limiting específico.
CORS estricto en producción
Lista blanca de orígenes, nunca * en producción con credenciales.
Refresh tokens con rotación
Para SPAs, implementa refresh tokens con rotación para minimizar riesgos.
Casos avanzados y edge cases
WebSockets y CORS
Los WebSockets no están sujetos a CORS, pero algunos servidores verifican el header Origin manualmente.
// Configuración en Socket.io
const io = require('socket.io')(server, {
cors: {
origin: "https://app.midominio.com",
methods: ["GET", "POST"],
credentials: true
}
});
CDN y CORS para assets estáticos
Para fuentes web, imágenes u otros recursos en CDN:
# Configuración en AWS S3 para CORS
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET"],
"AllowedOrigins": ["https://app.midominio.com"],
"ExposeHeaders": []
}
]
Microservicios y CORS interno
En arquitecturas de microservicios, considera:
- API Gateway que maneje CORS una sola vez
- Comunicación interna sin CORS (mismo origen interno)
- Headers personalizados para trazabilidad entre servicios
Checklist de implementación CORS
FAQ Extendida - Preguntas comunes respondidas
¿Cómo depuro problemas CORS en el navegador?
En DevTools → Network tab:
- Filtra por "OPTIONS" para ver preflight requests
- Revisa los headers de request y response
- Verifica que
Access-Control-Allow-Origincoincida exactamente - Usa "Copy as cURL" para probar fuera del navegador
¿Puedo usar wildcards en subdominios?
Sí, pero con limitaciones:
// VÁLIDO para subdominios específicos
Access-Control-Allow-Origin: https://*.midominio.com
// INVÁLIDO - no mezcles protocolos
Access-Control-Allow-Origin: *://app.midominio.com
// MEJOR PRÁCTICA: Validación dinámica en backend
const allowedPattern = /^https:\/\/(app|staging|api)\.midominio\.com$/;
¿Qué pasa con las redirects (301/302)?
Los redirects pueden perder headers CORS. Soluciones:
- Configurar CORS en el servidor que hace el redirect
- Evitar redirects en APIs (usar status codes apropiados)
- En Nginx/Apache, configurar CORS para todas las respuestas
¿CORS afecta a Server-Side Rendering (SSR)?
No, porque SSR ocurre en el servidor. Pero:
- Las llamadas desde el cliente (hydration) sí están sujetas a CORS
- Considera API routes en tu framework (Next.js, Nuxt) para evitar CORS