Lunes por la mañana. Son las 7:45 AM y estoy tomando mi segundo Red Bull del día cuando me llega un mensaje de uno de mis alumnos al WhatsApp: "Carlos, el sitio no funciona, sale un error raro en la consola". Abro Chrome DevTools y ahí está, el maldito error que he visto más veces que comerciales de Pilsener:
Access to XMLHttpRequest at "https://api.ejemplo.com" from origin
"http://localhost:3000" has been blocked by CORS policy:
No "Access-Control-Allow-Origin" header is present on the requested resource.
CORS. Cuatro letras que han hecho llorar a más developers que el finale de Avengers: Endgame. Si estás leyendo esto, probablemente acabas de perder 2 horas de tu vida googleando "CORS error fix" y copiando headers random de StackOverflow que no funcionan.
Tranquilo, cholo querido. Hoy te voy a explicar CORS de una forma tan simple que hasta Julian (mi hijo de 9 años) lo entendería.
CORS explicado para un niño de 5 años (o para tu project manager)
Imagínate que estás en tu casa (tu sitio web) y quieres pedirle azúcar al vecino (una API externa). Pero tu mamá (el navegador) es súper protectora y tiene una regla: "No puedes hablar con extraños".
- Tú: "Mamá, necesito pedirle datos al vecino"
- Mamá (navegador): "¿El vecino te dio permiso?"
- Tú: "Ehh... no sé"
- Mamá: "Entonces NO. CORS POLICY BLOCKED!"
Eso es literalmente CORS. Tu navegador siendo una mamá sobreprotectora que no te deja hacer peticiones a otros dominios a menos que ese dominio explícitamente diga: "Sí, está bien, pueden hablar conmigo".
¿Por qué existe esta tortura?
Antes de CORS, cualquier sitio web podía hacer peticiones a cualquier otro sitio en tu nombre. Imagínate entrando a gatitos-bonitos.com y que sin que te des cuenta, ese sitio haga transferencias desde tu banco porque tenías la sesión abierta. No muy divertido, ¿verdad?
CORS existe para protegerte. Es como el cinturón de seguridad: molesta, pero te salva la vida. El problema es que también te "protege" cuando estás desarrollando tu propia aplicación y necesitas conectarte a tu propia API.
Los errores CORS más comunes (y cómo sé que los has visto todos)
1. El clásico "No Access-Control-Allow-Origin"
Este es el Hello World de los errores CORS. Tu frontend en localhost:3000 intenta hablar con tu API en localhost:8000 y BAM! Error.
// Lo que intentas hacer (y no funciona)
fetch("http://localhost:8000/api/users")
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.log("MALDITO CORS!", err));
*Se eliminaron las comillas simples para evitar problemas de compatibilidad
2. El traicionero "Preflight request failed"
Este aparece cuando usas métodos HTTP fancy como PUT, DELETE, o cuando mandas headers custom. El navegador hace una petición OPTIONS primero para preguntar "¿puedo?" y tu servidor responde "¿qué es OPTIONS? No hablo ese idioma".
3. El misterioso "Credentials not supported"
Intentas mandar cookies o autenticación y el navegador te dice que no. Es como intentar entrar a una fiesta VIP con la pulsera del evento anterior.
Las "soluciones" que NO funcionan (pero que todos intentamos)
Seamos honestos, todos hemos intentado estas:
1. Deshabilitar CORS en Chrome
Sí, funciona... en TU computadora. Pero cuando tu cliente abre el sitio, sigue sin funcionar. Es como arreglar un carro con cinta adhesiva: se ve bien hasta que lo prendes.
2. El proxy mágico de package.json
{
"proxy": "http://localhost:8000"
}
Funciona en desarrollo con Create React App. En producción? Jajaja, no.
3. El modo "no-cors" (aka modo avestruz)
fetch(url, {
mode: "no-cors" // "Si no veo el error, no existe"
})
Esto no soluciona CORS. Solo oculta el error y te da una respuesta opaca que no puedes leer. Es como tapar el check engine del carro con cinta negra.
La solución que SIEMPRE funciona (en serio)
Después de 15 años peleando con CORS, aquí está mi solución infalible. No es elegante, no es fino, pero funciona el 100% de las veces.
Opción 1: Configurar tu servidor correctamente (la forma correcta)
Si controlas el servidor (y deberías), agrega estos headers:
Para PHP (porque sí, todavía uso PHP en 2025 y qué):
<?php
// Lo pongo al inicio de mi API, siempre
header("Access-Control-Allow-Origin: http://localhost:3000");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
// IMPORTANTE: Manejar el preflight
if ($_SERVER["REQUEST_METHOD"] == "OPTIONS") {
http_response_code(204);
exit();
}
?>
Para Node.js/Express (para los modernos):
const cors = require("cors");
app.use(cors({
origin: "http://localhost:3000",
credentials: true
}));
// O si eres valiente (solo en desarrollo!)
app.use(cors()); // Permite todo, como las fiestas en la casa del gran hermano
Para Python/Flask (para los cientificos de datos convertidos en web devs):
from flask_cors import CORS
app = Flask(__name__)
CORS(app, origins=["http://localhost:3000"])
Opción 2: El proxy reverso (cuando no controlas el servidor)
Si la API no es tuya y el dueño no quiere agregar CORS (típico), necesitas un intermediario. Es como pedirle a tu hermano mayor que hable con la chica que te gusta porque tú eres muy tímido.
Solución rápida con PHP (mi favorita porque funciona en cualquier hosting barato):
<?php
// proxy.php - El intermediario que salva el día
$url = $_GET["url"];
// Seguridad básica (no seas idiota, valida mejor esto en producción)
$allowed_domains = ["api.ejemplo.com", "api.otroejemplo.com"];
$parsed = parse_url($url);
if (!in_array($parsed["host"], $allowed_domains)) {
die("Nice try, hacker");
}
// Hacer la petición
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// Copiar headers si necesitas autenticación
$headers = getallheaders();
if (isset($headers["Authorization"])) {
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: " . $headers["Authorization"]]);
}
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Devolver con CORS habilitado
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
http_response_code($http_code);
echo $response;
?>
Ahora en tu JavaScript:
// En vez de llamar directo a la API externa
// fetch("https://api.externa.com/data")
// Llamas a tu proxy
fetch("http://tu-sitio.com/proxy.php?url=https://api.externa.com/data")
.then(response => response.json())
.then(data => console.log("¡Funcionó!", data));
Opción 3: La nuclear (solo para desarrollo)
Si estás desarrollando y te vale madre la seguridad temporalmente:
1. Usa ngrok para exponer tu localhost:
ngrok http 3000
Te da una URL https que funciona desde cualquier lugar. Liss siempre se sorprende cuando le muestro el sitio en su celular mientras todavía estoy desarrollando.
2. Extensión de Chrome (para los desesperados):
Instala "CORS Unblock" o "Allow CORS". Pero OJO: solo para desarrollo. Si le dices a tu cliente que instale esto, mereces que te despidan.
El enfoque que uso en producción (y que nunca falla)
Después de años de prueba y error, este es mi setup definitivo:
1. Mismo dominio cuando es posible:
API en api.tu-dominio.com y frontend en tu-dominio.com. Mismo dominio = menos problemas.
2. Lista blanca específica en el servidor:
$allowed_origins = [
"https://tu-dominio.com",
"https://www.tu-dominio.com",
"http://localhost:3000" // Solo si tu ambiente de dev está separado
];
$origin = $_SERVER["HTTP_ORIGIN"] ?? "";
if (in_array($origin, $allowed_origins)) {
header("Access-Control-Allow-Origin: $origin");
}
3. Siempre manejo OPTIONS:
El 50% de los problemas de CORS vienen de no manejar el preflight request. Es como no saludar cuando llegas a una reunión: técnicamente no es obligatorio, pero todos te van a mirar mal.
Debugging CORS como un pro
Cuando te encuentres con un error CORS, sigue este checklist:
1. ¿Qué dice exactamente el error?
Lee el mensaje completo. A veces dice exactamente qué header falta.
2. Revisa la pestaña Network:
- ¿Se hace la petición OPTIONS?
- ¿Qué headers está mandando?
- ¿Qué responde el servidor?
3. Prueba con curl:
curl -X OPTIONS https://tu-api.com/endpoint
-H "Origin: http://localhost:3000"
-H "Access-Control-Request-Method: POST"
-H "Access-Control-Request-Headers: Content-Type"
-v
Si funciona con curl pero no en el navegador, el problema es CORS. Si no funciona con curl, tu API está rota.
Los mitos de CORS que necesitas dejar de creer
Mito 1: "CORS es un problema del frontend"
No. CORS se soluciona en el backend. El frontend solo puede llorar.
Mito 2: "Access-Control-Allow-Origin: * es inseguro"
Depende. Para una API pública (como una de clima), está bien. Para tu API de banco, obviamente no. Usa tu cerebro.
Mito 3: "CORS no aplica para peticiones del servidor"
Correcto. CORS es una restricción del navegador. Por eso tu curl funciona pero tu fetch no. Por eso un proxy PHP funciona: PHP no es un navegador.
La verdad incómoda sobre CORS
CORS no es difícil. Es molesto. Es como configurar una impresora: no es ciencia de cohetes, pero siempre hay algo que no funciona a la primera.
La mayoría de developers odian CORS porque intentan solucionarlo sin entenderlo. Es como intentar armar un mueble con 30 tornillos y 9 tablas sin ver las instrucciones: posible, pero innecesariamente doloroso.
Mi consejo después de años peleando con esto: configúralo bien desde el principio. Gasta 30 minutos entendiendo CORS y ahórrate 30 horas debuggeando después.
¿Sigues teniendo problemas con CORS? ¿Tu solución es diferente? ¿Encontraste una forma más elegante de hacer el proxy? Compártela en los comentarios. Entre todos podemos crear la guía definitiva de CORS que deberíamos haber tenido hace 10 años.
Y si este artículo te salvó la vida (o al menos la tarde), compártelo. Hay un developer en algún lugar del mundo googleando "CORS error" en este momento, y merece encontrar una solución que realmente funcione.
Comentarios
Comparte tu opinión
No hay comentarios aún
Sé el primero en compartir tu opinión sobre este artículo.