← ← Volver a todas las notas

Error CORS: Guía Definitiva para Entenderlo, Solucionarlo y Evitar Problemas de Seguridad

2025-12-26 · Benja

CORS no es un error del servidor sino un mecanismo de seguridad del navegador. Aprender a configurarlo correctamente evitará dolores de cabeza en desarrollo y producción, y te ayudará a construir APIs más seguras

Error CORS: Guía Definitiva para Entenderlo, Solucionarlo y Evitar Problemas de Seguridad

¿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:

  1. Frontend: Tu aplicación en https://app.com hace fetch a https://api.com/data
  2. Navegador: Detecta origen cruzado y añade header Origin: https://app.com
  3. Servidor: Responde con los datos pero sin Access-Control-Allow-Origin: https://app.com
  4. 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:

  1. Filtra por "OPTIONS" para ver preflight requests
  2. Revisa los headers de request y response
  3. Verifica que Access-Control-Allow-Origin coincida exactamente
  4. 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

Comentarios

0 comentarios

Dejá tu comentario

Se publicará cuando sea aprobado.

Todavía no hay comentarios.