Cómo solucionar errores de CORS
“Blocked by CORS policy” no es un fallo de tu código, sino una protección del navegador para tus usuarios. La solución siempre está en el servidor que recibe la petición. Esta guía explica qué comprueba el navegador y ofrece configuraciones que funcionan en los casos más comunes.
Qué es CORS realmente
El navegador bloquea que el JavaScript de https://app.example.com lea respuestas de otro origen (esquema + host + puerto), como https://api.example.com, a menos que ese servidor lo permita. El servidor lo indica con headers de respuesta Access-Control-Allow-*. Las peticiones desde curl, Postman o tu backend no se ven afectadas, por eso “funciona en Postman” pero falla en el navegador.
Lee el mensaje de error
- “No ‘Access-Control-Allow-Origin’ header is present”: el servidor no envió headers de CORS, o devolvió una página de error (404, 500) sin ellos.
- “Response to preflight request doesn’t pass access control check”: la petición
OPTIONSfalló. No se gestionó, devolvió un error o no incluía los headers necesarios. - “The value of the ‘Access-Control-Allow-Origin’ header must not be the wildcard ‘*’ when credentials mode is ‘include’”: envías cookies, así que el servidor debe devolver el origen exacto.
Revisa la petición fallida en DevTools → Network, o comprueba los headers reales de la respuesta con el HTTP Header Checker.
Peticiones simples y preflight
Una petición es “simple” si es un GET, HEAD o POST con solo headers básicos y un cuerpo de formulario o texto plano. Cualquier otra cosa, como Content-Type: application/json, un header Authorization, PUT o DELETE, hace que el navegador envíe primero una petición OPTIONS de preflight. Tu servidor debe responder con estado 204 o 200 y estos headers:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600
Configuraciones que funcionan
Express con el paquete cors:
const cors = require('cors');
app.use(cors({
origin: ['https://app.example.com'],
credentials: true, // only if you send cookies
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
Nginx:
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age 600;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
proxy_pass http://backend;
}
El flag always es importante: sin él, Nginx elimina el header en las respuestas de error y el navegador muestra un error de CORS en lugar del 500 real. Genera headers para otros servidores (Vercel, Netlify, Cloudflare Workers, Apache) con el CORS Header Generator.
Cookies y credenciales
Para enviar cookies entre orígenes, la petición necesita credentials: 'include' (fetch) y el servidor debe responder con Access-Control-Allow-Credentials: true y el origen exacto, nunca *. La cookie también necesita SameSite=None; Secure. Si devuelves el origen desde una lista, envía además Vary: Origin para que las cachés no sirvan los headers de un sitio a otro.
Lo que no debes hacer
- No devuelvas cualquier header
Origincon credenciales activadas: así cualquier sitio web podría leer los datos de tus usuarios. - No “arregles” CORS con una extensión del navegador ni con un proxy público; solo oculta el problema en tu máquina.
- Si la API es tuya y está en el mismo sitio, un reverse proxy (mismo origen) elimina CORS por completo.
Reproduce la petición del navegador con el API Tester, o conviértela en código con cURL to Code.