Solucionar problemas CORS en localhost con un Gateway

Cómo desarrollador he tenido que lidiar con los CORS muchas veces mientras estoy desarrollando un frontal que tira de un backend. Normalmente tiene una solución rápida, habilitas CORS en el backend para cualquier dominio y listo:

 <?php
 header("Access-Control-Allow-Origin: *");

Pero esta solución tiene inconvenientes:
  • Tienes que acordarte de quitar las cabeceras del backend antes de pasar a producción. Aunque no es para echarse las manos a la cabeza, tarde o temprano las cabeceras incorrectas acabarán en el API online.
  • Y si no tienes ningún control sobre el API, sucede que simplemente no puedes cambiar el backend.

Solución con un Gateway

Usando Nginx como Proxy Inverso puedes crear un Gateway hacia la API real y responder al cliente en desarrollo con los encabezados de respuesta que necesites, en nuestro caso con las CORS, pero es posible controlar caches y otros encabezados con los que hay que lidiar al desarrollar.

Es más, un Gateway lo puede usar un equipo formado por varios desarrolladores que así compartirán el API ahorrando tiempo y problemas, ya que todos los desarrolladores van a usar la misma versión del API en el backend.

Así se configura:

Paso 1. Chrome no soporta CORS en Localhost

Chrome no soporta peticiones CORS desde localhost, la solución pasa por añadir una entrada al fichero /etc/hosts que simule un dominio local:

# echo "127.0.0.1 local.example.org" >> /etc/hosts

Paso 2. Nginx como proxy inverso

Voy a utilizar un contenedor Docker con Nginx como proxy inverso. Utilizaré como volumen /etc/nginx/conf.d que es el lugar dónde voy a colocar los ficheros de configuración de Nginx.

$ docker run --name nginx -p80:80 -v /etc/nginx/conf.d:/etc/nginx/conf.d -d nginx 

Paso 3. Configurar CORS en el dominio local

El API debe responder a la solicitud de comprobación preliminar OPTIONS con al menos los siguientes encabezados de respuesta necesarios para CORS:

  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Allow-Origin

Que configuro en el fichero local.example.org.conf que he creado con este contenido:

# /etc/nginx/conf/local.example.org.conf

server {

    access_log off;
    error_log off;

    listen 80;
    server_name local.example.org;
    
    location / {
          
    if ($request_method = 'OPTIONS') {

      add_header Access-Control-Allow-Origin '*';
      add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
      add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';                
      #
      # Tell client that this pre-flight info is valid for 20 days
      #
      add_header 'Access-Control-Allow-Credentials' 'true';
      add_header 'Access-Control-Max-Age' 1728000;
      add_header 'Content-Type' 'text/plain; charset=utf-8';
      add_header 'Content-Length' 0;
      return 204;
    }


    client_max_body_size    2000m;
    client_body_buffer_size 512k;
    proxy_connect_timeout 600s;
    proxy_send_timeout   600;
    proxy_read_timeout   600;
    proxy_buffer_size    32k;
    proxy_buffers     16 32k;
    proxy_busy_buffers_size 64k;
    proxy_temp_file_write_size 64k;    
    proxy_redirect     localhost:8080   http://example.org/api;
    proxy_pass         http://localhost:8080;
    
    proxy_set_header   Host $host;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;                

    add_header   Access-Control-Allow-Origin * always;      
  }

}

Hay que habilitar el Gateway CORS configurando un método OPTIONS con el tipo de integración simulado para devolver los encabezados de respuesta. Además, los métodos reales habilitados para CORS también deben devolver el encabezado Access-Control-Allow-Origin:’*’ en al menos su respuesta 200.

Nota Hay que cambiar localhost:8080 por la ubicación de la API.

Comprobar

Y listo, sólo falta comprobar que las cabeceras CORS llegan en las respuestas:

# curl -H "Origin: localhost" -H "Access-Control-Request-Method: POST" -X OPTIONS --verbose  http://local.example.org/api
*   Trying 127.0.0.1...
* Connected to local.example.org (127.0.0.1) port 80 (#0)
> OPTIONS /api HTTP/1.1
> User-Agent: curl/7.47.0
> Accept: */*
> Origin: localhost
> Access-Control-Request-Method: POST
> 
< HTTP/1.1 204 No Content
< Server: nginx
< Connection: keep-alive
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
< Access-Control-Allow-Headers: Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range
< Access-Control-Allow-Credentials: true
< Access-Control-Max-Age: 1728000
< Content-Type: text/plain; charset=utf-8
< Content-Length: 0

César Maeso


Creative Commons License

Esta obra está bajo una licencia de Creative
Commons Reconocimiento-NoComercial-CompartirIgual
4.0 Internacional
.