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