workspace/projects/gamilit/docs/95-guias-desarrollo/GUIA-CORS-PRODUCCION.md
rckrdmrd 8b12d7f231 fix(cors): Resolve duplicate CORS headers in production
## Problem
CORS error: "Access-Control-Allow-Origin header contains multiple values"
caused by both Nginx and NestJS sending CORS headers.

## Solution
- NestJS handles CORS exclusively (main.ts)
- Nginx acts as SSL proxy only (no CORS headers)
- Updated .env.production.example with HTTPS origins
- Created GUIA-CORS-PRODUCCION.md with complete documentation

## Files Changed
- .gitignore: Allow .env.*.example files
- apps/backend/.gitignore: Allow .env.*.example files
- apps/backend/.env.production.example: HTTPS CORS config
- apps/frontend/.env.production.example: HTTPS/WSS config
- docs/95-guias-desarrollo/GUIA-CORS-PRODUCCION.md: Full guide

## Production Steps
1. Update .env.production files with HTTPS origins
2. Remove CORS headers from Nginx config
3. Rebuild frontend, restart backend

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 10:24:01 -06:00

4.5 KiB

GUIA-CORS-PRODUCCION.md

Configuración CORS para Producción - GAMILIT

Fecha: 2025-12-18 Problema resuelto: Error Access-Control-Allow-Origin contains multiple values


1. Descripción del Problema

Al hacer requests desde el frontend (puerto 3005) al backend (puerto 3006) en producción con HTTPS, se recibe el error:

Access to XMLHttpRequest at 'https://74.208.126.102:3006/api/v1/auth/register'
from origin 'https://74.208.126.102:3005' has been blocked by CORS policy:
The 'Access-Control-Allow-Origin' header contains multiple values
'https://74.208.126.102:3005, https://74.208.126.102:3005', but only one is allowed.

2. Causa Raíz

El header Access-Control-Allow-Origin se está enviando DOS VECES:

  1. Nginx (proxy SSL) agrega headers CORS
  2. NestJS (backend) también agrega headers CORS via app.enableCors()

Cuando ambos envían el header, el navegador ve valores duplicados y rechaza la respuesta.


3. Solución Definitiva

Regla de Oro: Solo NestJS maneja CORS

Nginx debe actuar únicamente como proxy SSL sin agregar headers CORS.

3.1 Configuración Backend (.env.production)

# CORS - CONFIGURACIÓN CRÍTICA
# ============================================================================
# ADVERTENCIA: HEADERS CORS DUPLICADOS
# ============================================================================
# Si usas Nginx como proxy SSL, NO agregar headers CORS en Nginx.
# NestJS maneja CORS internamente en main.ts.
# Headers duplicados causan error:
#   "The 'Access-Control-Allow-Origin' header contains multiple values"
# ============================================================================

# Incluye HTTPS y HTTP para compatibilidad durante transición
CORS_ORIGIN=https://74.208.126.102:3005,https://74.208.126.102,http://74.208.126.102:3005,http://74.208.126.102
ENABLE_CORS=true

3.2 Configuración Frontend (.env.production)

# SSL CONFIGURADO - Usar HTTPS/WSS
VITE_API_HOST=74.208.126.102:3006
VITE_API_PROTOCOL=https
VITE_WS_HOST=74.208.126.102:3006
VITE_WS_PROTOCOL=wss

3.3 Configuración Nginx (SIN CORS)

# /etc/nginx/sites-available/gamilit-backend
server {
    listen 3006 ssl;
    server_name 74.208.126.102;

    ssl_certificate /etc/ssl/certs/gamilit.crt;
    ssl_certificate_key /etc/ssl/private/gamilit.key;

    location / {
        proxy_pass http://127.0.0.1:3007;  # PM2 cluster interno
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;

        # ⚠️ NO AGREGAR headers CORS aquí
        # NestJS los maneja internamente
    }
}

4. Verificación

4.1 Verificar headers con curl

# Verificar que solo hay UN header Access-Control-Allow-Origin
curl -I -X OPTIONS \
  -H "Origin: https://74.208.126.102:3005" \
  -H "Access-Control-Request-Method: POST" \
  https://74.208.126.102:3006/api/v1/auth/register

# Salida esperada (UN solo header):
# Access-Control-Allow-Origin: https://74.208.126.102:3005
# Access-Control-Allow-Credentials: true
# Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,OPTIONS

4.2 Verificar configuración Nginx

# Buscar headers CORS duplicados en configuración
grep -r "Access-Control" /etc/nginx/

Si encuentra líneas como add_header Access-Control-Allow-Origin, elimínelas.


5. Pasos de Deploy

  1. Backend: Actualizar .env.production con HTTPS origins
  2. Frontend: Actualizar .env.production con HTTPS/WSS
  3. Nginx: Remover cualquier header CORS
  4. Reiniciar servicios:
    sudo systemctl reload nginx
    pm2 restart gamilit-backend
    cd /path/to/frontend && npm run build
    

6. Troubleshooting

Error persiste después de la configuración

  1. Verificar que Nginx no tenga headers CORS en ningún include
  2. Revisar si hay otro proxy (CloudFlare, etc.) agregando headers
  3. Limpiar cache del navegador (F12 > Network > Disable cache)

Error "CORS blocked request" en logs

  • El origen que hace la petición no está en CORS_ORIGIN
  • Verificar que el protocolo coincide (https vs http)

7. Referencias

  • Backend CORS: apps/backend/src/main.ts líneas 27-46
  • Config CORS: apps/backend/src/config/app.config.ts
  • Ejemplo .env: apps/backend/.env.production.example