Definición
CORS (siglas de Cross-Origin Resource Sharing) es un mecanismo de seguridad implementado por los navegadores que controla qué dominios pueden hacer peticiones HTTP a tu servidor desde JavaScript. Existe para proteger a los usuarios: sin CORS, cualquier web maliciosa podría hacer peticiones a tu cuenta bancaria mientras tienes la sesión abierta.
Por defecto, el navegador aplica la same-origin policy: el JavaScript de tudominio.com solo puede hacer fetch a tudominio.com. Si quieres permitir que otrodominio.com haga peticiones a tu API, tienes que decirlo explícitamente con cabeceras HTTP especiales — eso es CORS.
Es la causa #1 de frustración cuando un dev frontend intenta llamar a una API externa por primera vez. El típico error en consola:
Access to fetch at 'https://api.midominio.com/datos' from origin
'http://localhost:3000' has been blocked by CORS policy
Cómo funciona
CORS define headers HTTP que el servidor debe enviar para autorizar peticiones cross-origin:
| Header de respuesta | Función |
|---|---|
Access-Control-Allow-Origin | Qué dominios pueden llamar (* para todos, o un dominio concreto) |
Access-Control-Allow-Methods | Qué métodos HTTP están permitidos (GET, POST, etc.) |
Access-Control-Allow-Headers | Qué headers personalizados acepta |
Access-Control-Allow-Credentials | Si acepta cookies/auth en cross-origin |
Access-Control-Max-Age | Cuánto cachear el resultado del preflight |
Para peticiones "simples" (GET/POST básicas), el navegador envía la petición directamente y mira las cabeceras de respuesta. Si no autorizan tu origen, bloquea la respuesta.
Para peticiones "complejas" (PUT, DELETE, custom headers, body JSON...), el navegador envía un preflight: una petición OPTIONS previa para preguntar "¿me dejas hacer esto?". Solo si la respuesta es afirmativa, hace la petición real.
Ejemplo práctico
API Node.js (Express) configurando CORS:
import express from 'express';
import cors from 'cors';
const app = express();
// CORS abierto a todos (solo desarrollo)
app.use(cors());
// CORS restrictivo en producción
app.use(cors({
origin: ['https://imdica.es', 'https://www.imdica.es'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400 // cachea preflight 24h
}));
app.get('/api/productos', (req, res) => {
res.json({ productos: [...] });
});
Lo que el navegador ve:
Petición:
OPTIONS /api/productos HTTP/1.1
Origin: https://imdica.es
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Authorization
Respuesta del servidor (preflight):
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://imdica.es
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Petición real:
GET /api/productos HTTP/1.1
Authorization: Bearer xyz
Mitos comunes
- "CORS es seguridad del servidor": NO. CORS lo aplica el NAVEGADOR. Una petición curl o desde otro servidor ignora completamente CORS. La seguridad real está en autenticación + autorización del servidor.
- "Si pongo
Allow-Origin: *no es seguro": depende. Para APIs públicas sin auth (datos abiertos) es perfectamente válido. El problema solo aparece si combinas*concredentials: true. - "CORS es lo mismo que CSRF": no. CSRF es otro vector (peticiones automáticas con cookies). CORS y CSRF se protegen distinto.
Errores comunes y cómo arreglarlos
"No 'Access-Control-Allow-Origin' header is present"
El servidor no envía el header. Soluciones:
- Si controlas el servidor: añade el middleware CORS (ej.
app.use(cors())en Express) - Si NO controlas el servidor: usa un proxy. En dev con Next.js puedes usar
next.config.js → rewritespara que tu app actúe como proxy - Para testing: extensiones de navegador que desactivan CORS (solo dev, NUNCA en producción ni para usuarios)
"Request header field X is not allowed"
El header personalizado (ej. Authorization) no está en Allow-Headers. Añádelo en la config del servidor.
"The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when credentials mode is 'include'"
No puedes combinar * con cookies/credentials. Especifica el dominio exacto o lista de dominios permitidos.
Funciona en Postman/curl pero no en navegador
Confirma que es CORS. Postman y curl no aplican same-origin policy — son herramientas, no navegadores. Si funciona ahí pero no en navegador, es 100% CORS.
CORS en hosting estático (Hostinger, GitHub Pages...)
Si tu web está estática (HTML+JS sin backend) y llamas a una API externa:
- La API externa debe permitir tu dominio en CORS
- No puedes "arreglarlo" tú desde el cliente
- Si la API no soporta CORS, opciones:
- Usar un proxy serverless (Cloudflare Workers, Vercel Functions)
- Pedir al dueño de la API que añada tu dominio
- JSONP (anticuado, solo GET, evítalo)
Cuándo y cómo configurar CORS
En APIs públicas (datos abiertos sin auth):
Access-Control-Allow-Origin: *
En APIs privadas (con autenticación):
Access-Control-Allow-Origin: https://midominio.com
Access-Control-Allow-Credentials: true
En producción NUNCA * con credentials. Lista los dominios uno a uno.
Referencias
- MDN — CORS — referencia exhaustiva
- Same-origin policy
- enable-cors.org — ejemplos de configuración por servidor