Cuando una página web realiza un pedido a un recurso en otro dominio (diferente protocolo, subdiminio, dominio o puerto), realiza lo que se llama un "pedido HTTP cross-origin". Por razones de seguridad, los navegadores restringen los pedidos HTTP cross-origin que se inician desde un script (JavaScript), como pueden ser los realizados utilizando el objeto XMLHttpRequest
. Para realizar pedidos legítimos a diferentes dominios, hay que implementar el mecanismo CORS o Cross Origin Resource Sharing.
El estándar CORS consiste en agregar headers específicos en los pedidos HTTP y en las respuestas correspondientes para que el servidor informe al navegador qué recursos están disponibles. Solo aquellos recursos disponibles serán accesibles desde dicho navegador.
Adicionalmente, ciertos pedidos como PUT
o DELETE
deben ser precedidos de un pedido del tipo OPTIONS
, llamado preflight, que debe ser aprobado por el servidor antes del envío del pedido original.
Un pedido simple es aquel que usa verbos GET
, POST
o HEAD
y no contiene headers especiales. Estos pedidos tienen esta forma:
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[XML Data]
El header de respuesta Access-Control-Allow-Origin
es el que indica si el recurso es accesible. Al ser *
, el acceso está garantizado. Si se deseara, en cambio, bloquar el acceso, se podría especificar el valor http://foo.example
.
Si, en cambio, se usa otro verbo o se incluyen headers propios, el pedido se vería así:
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache
<?xml version="1.0"?><person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[payload]
En este caso, el pedido OPITONS
pide autorización utilizando los headers Access-Control-Request-Method
y Access-Control-Request-Headers
. La respuesta, autioriza respondiendo con los headers Access-Control-Allow-Origin
,
Access-Control-Allow-Methods
y Access-Control-Allow-Headers
.
En particular, las cookies y los headers de autenticación no son enviados en pedidos cross-origin. Para permitir el envío de esta información, es necesario activar la propiedad withCredentials
del objeto XMLHttpRequest
.
Es importante notar que cuando el servidor contesta un pedido con credenciales, debe especificar el dominio en el header Access-Control-Allow-Origin
y no responder solo con *
.
El módulo cors
que se encuentra en npm, expone un middleware que facilita la operatoria con los pedios y las respuestas que deben seguir el estandard CORS.
Se instala vía npm
:
npm install cors
Para habilitar los pedidos CORS a todo nivel, solo se necesita incluir el middleware de esta manera:
var cors = require('cors');
app.use(cors());
Para más información y opciones de configuración, referirse a la documentación de dicho módulo.
Para que los pedidos relizados con el servcio $http
incluyan credenciales (cookies, etc), es necesario incluir la opción withCredentials: true
en cada pedido:
$http({
method: 'GET',
url: 'server.com',
withCredentials: true
});
También es posible configurar el envío de credenciales de manera global durante la etapa de configuración de AngularJS:
app.config(function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
});
- HTTP access control (CORS) en MDN.
- Módulo cors de Node.JS/Express.