Cómo Solucionar el Error CORS: No Access-Control-Allow-Origin Header Is Present

Cómo Solucionar el Error CORS: No Access-Control-Allow-Origin Header Is Present
Anuncio relacionado

Entendiendo la Política del Mismo Origen y el Error CORS

El error Access to fetch at '...' from origin '...' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource es probablemente una de las advertencias más comunes y frustrantes a las que se enfrenta un desarrollador frontend y backend. Para resolverlo de raíz, es imprescindible entender que no se trata de un fallo de red ni de un error de caída de servidor: es un mecanismo de defensa implementado activamente por el navegador web.

Este mecanismo se basa en la Same-Origin Policy (SOP) o Política del Mismo Origen. Por defecto, un script que se ejecuta en el navegador (por ejemplo, una SPA hecha en React o Vue en http://localhost:3000) solo tiene permiso para realizar peticiones asíncronas (vía fetch o XMLHttpRequest) al mismo protocolo, dominio y puerto desde el que fue descargado. Cuando intentas consumir una API ubicada en http://api.midominio.com o en otro puerto como http://localhost:8080, el navegador detecta una petición de origen cruzado (Cross-Origin).

Para permitir este tráfico, el servidor receptor debe responder explícitamente con cabeceras HTTP específicas bajo el estándar CORS (Cross-Origin Resource Sharing). Si el servidor no envía la cabecera Access-Control-Allow-Origin, o si los valores devueltos no coinciden con el origen de la aplicación cliente, el navegador intercepta la respuesta y bloquea el acceso a los datos por parte de JavaScript, arrojando el error en la consola de depuración.

Análisis Profundo de las Causas del Error

Existen diversos escenarios técnicos por los cuales este error se desencadena en producción y en entornos de desarrollo local:

  • Ausencia total de cabeceras CORS en el backend: El servidor procesa la petición con éxito (incluso devolviendo un código HTTP 200 OK), pero no adjunta la cabecera Access-Control-Allow-Origin en su respuesta.
  • Fallo en la petición preflight (solicitud preliminar OPTIONS): Las peticiones que no son consideradas 'simples' (por ejemplo, aquellas que usan métodos como PUT, DELETE, o cabeceras personalizadas como Authorization: Bearer <token> o Content-Type: application/json) obligan al navegador a enviar primero una petición HTTP con el método OPTIONS. Si el servidor rechaza este preflight con un código 401 Unauthorized, 403 Forbidden o 404 Not Found, la petición principal es cancelada inmediatamente.
  • Incompatibilidad de credenciales y comodines: Si la petición frontend envía cookies o cabeceras de autorización HTTP (usando credentials: 'include' en fetch o withCredentials: true en Axios), el estándar CORS prohíbe estrictamente el uso del comodín Access-Control-Allow-Origin: *. En estos casos, el servidor debe responder obligatoriamente con el origen exacto y la cabecera Access-Control-Allow-Credentials: true.
  • Pérdida de cabeceras en Proxies Inversos: Servidores web intermedios como Nginx, Cloudflare, Apache o API Gateways pueden filtrar, sobreescribir o fallar al propagar las cabeceras CORS generadas por el servicio backend.

Método 1: Solución en Servidores Node.js con Express

En el ecosistema de Node.js, la forma más limpia y robusta de gestionar CORS es mediante el middleware oficial cors, controlando tanto las peticiones regulares como las solicitudes de tipo OPTIONS.

Instalación y configuración dinámica

Evita el uso indiscriminado de comodines abiertos en producción. Implementa una lista blanca dinámica (whitelist):

const express = require('express');
const cors = require('cors');
const app = express();

const allowedOrigins = [
  'http://localhost:3000',
  'https://www.midominio.com'
];

const corsOptions = {
  origin: function (origin, callback) {
    // Permitir peticiones sin origen (como curl o apps móviles locales)
    if (!origin) return callback(null, true);
    if (allowedOrigins.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('Bloqueado por política CORS'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
  credentials: true
};

// Aplicar CORS globalmente antes de las rutas
app.use(cors(corsOptions));

// Responder explícitamente a todas las peticiones preflight OPTIONS
app.options('*', cors(corsOptions));

app.get('/api/datos', (req, res) => {
  res.json({ mensaje: 'Petición autorizada sin fallos de CORS' });
});

app.listen(8080, () => console.log('API escuchando en puerto 8080'));

Método 2: Configuración a Nivel de Proxy Inverso en Nginx

Si tu API está construida en lenguajes como Go, Python, PHP o Rust, delegar el manejo de CORS a Nginx suele ser la opción más eficiente y desacoplada. Nginx debe gestionar de forma autónoma la petición preliminar OPTIONS devolviendo un estado 204 No Content sin sobrecargar la aplicación.

Edita tu bloque server o location dentro de /etc/nginx/sites-available/tu-sitio.conf:

location /api/ {
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '$http_origin' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH' always;
        add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, Origin, X-Requested-With' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Max-Age' 86400; # Cache preflight por 24 horas
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
    }

    proxy_pass http://127.0.0.1:5000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;

    # Inyectar cabeceras para las respuestas estándar
    add_header 'Access-Control-Allow-Origin' '$http_origin' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
}

Tras guardar los cambios, verifica la sintaxis del archivo ejecutando sudo nginx -t y recarga el servicio con sudo systemctl reload nginx.

Método 3: Configuración en Python con FastAPI y Django

En FastAPI

FastAPI incluye un middleware nativo de Starlette altamente optimizado para resolver CORS en milisegundos:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost:3000",
    "https://app.midominio.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

En Django

Para Django se requiere el paquete estándar django-cors-headers. Tras instalarlo mediante pip install django-cors-headers, debes agregarlo a tus aplicaciones instaladas y posicionar su middleware lo más arriba posible dentro de settings.py:

INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # Debe ubicarse antes de CommonMiddleware
    'django.middleware.common.CommonMiddleware',
    ...
]

# Orígenes permitidos de forma estricta
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "https://midominio.com",
]
CORS_ALLOW_CREDENTIALS = True

Método 4: Uso de Proxy en Entornos Locales de Desarrollo

Cuando te encuentras desarrollando el frontend (por ejemplo, con Vite, Webpack o Next.js) y no tienes control sobre el código del backend o este aún se encuentra en un servidor staging sin CORS habilitado, la mejor solución es redirigir el tráfico a través de un servidor proxy de desarrollo local.

Dado que el proxy corre en un entorno Node.js fuera del navegador, la Same-Origin Policy no aplica para él. El navegador solo se comunicará con su propio servidor de desarrollo local.

Configuración en Vite (vite.config.js)

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: 'https://api.externa-bloqueada.com',
        changeOrigin: true,
        secure: false,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
});

Con esta directiva, cualquier llamada que tu aplicación realice a /api/usuarios será recibida por el servidor local de Vite en el puerto 3000 y reenviada de forma transparente a https://api.externa-bloqueada.com/usuarios, anulando por completo cualquier restricción CORS en la etapa de maquetación y pruebas locales.

Buenas Prácticas de Seguridad en Producción

  1. Nunca utilices * junto a credenciales: Como se mencionó, viola las especificaciones de seguridad W3C y ningún navegador moderno procesará la respuesta.
  2. Cuidado con reflejar el encabezado Origin sin validar: Copiar ciegamente cualquier origen recibido en req.headers.origin y plasmarlo en Access-Control-Allow-Origin crea una vulnerabilidad crítica equivalente a exponer tu API públicamente sin restricciones.
  3. Gestiona el tiempo de vida de la caché preflight: Configura Access-Control-Max-Age con valores entre 3600 (1 hora) y 86400 (24 horas) para evitar que cada petición individual de tu frontend ejecute una petición previa OPTIONS, reduciendo drásticamente la latencia percibida por el usuario.
Anuncio
Seguir Leyendo

Guías y Soluciones Relacionadas

Windows Apps Juegos
Windows

Cómo Solucionar el Error de Bucle de Reparación Automática en Windows 10 y 11 Paso a Paso

Descubre por qué tu ordenador entra en un bucle infinito de reparación automática al iniciar. Sigue nuestros métodos probados para recuperar el acceso a tu sistema operativo sin perder datos.

Leer guía completa →
Apps

Cómo Solucionar el Error STATUS_BREAKPOINT en Google Chrome de Forma Definitiva

El molesto error STATUS_BREAKPOINT en Google Chrome interrumpe tu navegación de forma inesperada. En esta guía te enseñamos los mejores métodos para solucionarlo definitivamente.

Leer guía completa →
Apps

Cómo Solucionar el Error de Bucle de Inicio de Sesión en Microsoft Teams Paso a Paso

¿Microsoft Teams te redirige constantemente y no te deja iniciar sesión? Aprende a limpiar las credenciales almacenadas y resolver este error de forma definitiva.

Leer guía completa →