En mis tres anteriores post me centré en JSONP, la solución soportada actualmente por cualquier navegador para hacer llamadas a servicios JSON ubicados en dominios diferentes al actual (ver índice al final de este post). Ahora, tras quince dís sin tiempo para nada (con Codecamp y TTT para Microsoft por el medio), por fin saco un par de horitas para escribir esta últma parte de la serie, dedicada a los estándares.
Las limitaciones del objeto XmlHttpRequest a la hora de hacer peticiones desde el navegador a páginas y recursos ubicados en otros dominios son de sobra conocidas. Ello ha hecho que los programadores busquen atajos para conseguir esta funcionalidad y saltarse esta barrera. Hasta ahora hemos visto cómo JSONP nos permite conseguirlo gracias al uso de la etiqueta <script>, y cómo se le puede sacar partido en muchas ocasiones. No obstante esta técnica tiene bastantes limitaciones:
· Sólo sirve para peticiones GET
· Los datos deben devolverse en formato JSON, o sea, JavaScript.
· La información que se puede enviar es muy limitada (únicamente datos planos como parámetros de la URL)
· El servidor debe colaborar explícitamente con el método para devolver el código envuelto en una llamada a un método JavaScript
· No existe un método estándar para controlar el acceso a este tipo de recursos remotos por parte del servidor que los provee, es decir, decidir quién y cuándo puede acceder al recurso.
Conscientes de esta necesidad en el World Wide Web Consortium (W3C), el organismo encargado de regular los estándares para la Web, han estado trabajando en una modificación del modo de funcionamiento habitual del objeto XmlHttpRequest para dotarlo de capacidades de acceso a otros dominios, así como de las convenciones necesarias para que los servidores que ofrecen servicios puedan regular el acceso de los navegadores a los mismos. El último borrador en el momento de escribir esto es de marzo de este año, lo escribe una persona que trabaja para el navegador Opera, y se puede leer aquí: Cross-Origin Resource Sharing. A partir de ahora en este texto le llamaré CORS a este protocolo, para facilitar la lectura.
Funcionamiento básico de CORS
El comportamiento por defecto del objeto XmlHttpRequest impide que se puedan hacer llamadas a recursos en otros dominios, impidendo así la realización de ataques de XSS y de XSRF, como ya hemos visto en los artículos anteriores. Lo que pretende la W3C con CORS es definir un protocolo para que los servidores Web y los navegadores colaboren a la hora de definir políticas de seguridad de acceso entre dominios.
Así, en su forma más básica, CORS define que cuando se haga una llamada con XmlHttpRequest a un dominio diferente del actual, dicha llamada debe incluir automáticamente una cabecera llamada "Origin" que indicará al servidor de destino el protocolo, dominio y puerto desde el que se está originando la petición. Esta cabecera difiere de la cabecera "Referer" en que no se indica ningún tipo de ruta, por lo que las preocupaciones de privacidad de los posibles usuarios son menores.
Con esta cabecera el servidor debe decidir si permite o no el acceso al recurso, devolviendo en su respuesta una cabecera de tipo Access-Control-Allow-Origin en la que indica al navegador qué dominios son permitidos como origen de las peticiones. Por ejemplo, puede devolver:
Access-Control-Allow-Origin: http://www.tuservidor.com
o, en muchos casos:
Access-Control-Allow-Origin: *
que significa que admite peticiones desde cualquier dominio externo.
Al recibir esta cabecera el navegador sabe si debe permitir o no que se realice la llamada. De esta forma se protege a los usuarios de un uso indebido de JavaScript para traspasar u obtener información entre dominios debido a alguna vulnerabilidad en el código de la página que permita inyectar JavaScript para hacer XSS o XSRF.
Por defecto, si el servidor no incluye esta cabecera el navegador bloquea la llamada, con lo que el comportamiento es exactamente el mismo que el tradicional.
Lo mejor es que realmente los programadores del lado cliente (del JavaScript del navegador) no tienen que hacer nada especial para soportar estas llamadas entre dominios, ya que pueden usar el objeto XmlHttpRequest como ya están acostumbrados a hacerlo y sobre lo que ya he hablado en este blog anteriormente, sólo que en la URL de destino incluyen la ruta completa, con el nombre de otro dominio.
El protocolo define todavía más cabeceras para cuestiones algo más especializadas, como por ejemplo para cachear el resultado de una valoración de seguridad y cuánto tiempo permanece guardada. Otra cosa muy interesante es lo que el borrador de CORS denomina "preflight requests". Se trata de una cómo provocar una petición previa por parte del navegador que, antes de solicitar el verdadero recurso en el que el código está interesado, lanza una petición con el método HTTP OPTIONS en el que el servidor le devuelve las condiciones de seguridad de las llamadas desde ese origen. El navegador las cachea y reutiliza durante un tiempo determinado. El programador no tiene que hacer dos llamadas para consegui esto, sino que con una única llamada mediante XmlHttpRequest, construida de determinada manera, provoca la obtención de esta información. Si tienes interés en profundizar en el estos detalles puedes leerte el borrador de CORS o también el documento que tiene al respecto la gente de la fundación Mozilla (que merendilla :-P).
Pero ¿qué conseguimos con esto?
Puede que a estas alturas te estés diciendo: "Vale, estupendo, el servidor indica qué dominios tienen acceso pero ¿cómo impides que con esto alguien haga una llamada al servicio con cualquier otro método manual, sin pasar por el navegador?¿Qué modo de proteger mi aplicación del servidor es esta?".
La respuesta es que este protocolo no pretende proteger el acceso a tus aplicaciones de servidor. No es un método de seguridad para servicios en Internet. Lo que busca es proteger a los usuarios que navegan usando un navegador estándar de problemas en el código de las páginas que visitan, de modo que de forma inadvertida para ellos un pirata pueda usar técnicas de inyección de código para obtener acceso a su información. Revisa el tercer post de esta serie, más abajo, para recordar cómo son este tipo de ataques.
Esta es una distinción fundamental, ya que muchos programadores a los que se les habla de este protocolo piensan que les servirá para proteger sus recursos en el servidor, y es justo al contrario: es para proteger el uso indebido de código de cliente. Así que ojo con esto. La protección de los recursos del servidor no tiene nada que ver con esto y cada aplicación deberá usar el método, estándar o no, que considere oportuno (como el uso de Cookies cifradas, como ya he analizado en este blog).
Por otra parte el hecho de que podamos usar XmlHttpRequest para este tipo de llamadas nos quita de un plumazo todas las limitaciones que comentaba al principio del post, ya que podemos enviar peticiones GET y POST, cuaqluier tipo de información de cualquier longitud y en cualquier formato de texto (XML, JSON...), y tampoco necesitamos colaboración explícita por parte del servidor más que la inclusión de la cabecera, algo que no complica la programación en absoluto.
Así que si expones un servicio en cualquier formato y quieres darle un punto más de seguridad a tus usuarios incluye la cabecera Access-Control-Allow-Origin para indicar desde qué dominios podrá ser utilizado.
Soporte de navegadores
En la actualidad, y a pesar de que se trata todavía de un borrador, la especificación de este protocolo está soportada por todos los navegadores recientes, es decir, por Firefox 3.5, Safari 4 y Chrome 2.0.
"¿Eh? Un momento, ¿no acabas de decir todos los navegadores recientes?. ¿Y donde está Internet Explorer 8.0? ¿Y Opera?"
Curiosamente, aunque alguien de Opera es el responsable del borrador en la W3C, el fabricante europeo parece que no se ha decidido a implementarlo todavía.
Por otro lado, lamentablemente, Microsoft ha ido por su cuenta con este asunto, ofreciendo su propia implementación y permitiendo hacer lo mismo mediante un nuevo objeto especial llamado XDomainRequest. En realidad, como verás si te lees el enlace anterior, el uso es casi igual al que haríamos con el objeto XmlHttpRequest, pero tiene pequeñas diferencias. Pero por lo demás funciona prácticamente igual y acepta las mismas cabeceras desde el servidor.
Esto, como puedes imaginar, ha creado una nueva corriente anti-IE entre la comunidad más activa en el desarrollo Web y los estándares. Mi opinión personal es también la misma: que deberían haber seguido el borrador al pie de la letra, con el objeto XmlHttpRequest y no con un objeto nuevo que le complique la vida a los scripts multi-navegador. Y más si pensamos que con IE8 Microsoft ha dado un giro espectacular hacia el soporte estricto de los estándares Web, alejándose de la nefasta corriente con la que tontearon con IE6 (motivo principal del clamor popular para eliminar esta infame versión 6.0 del navegador de Microsoft)
¿Por qué decidió Microsoft crear un objeto propio y no hacer como los demás lo que indica el borrador del estándar?. El motivo oficial es que consideran que CORS en su forma actual no es totalmente seguro. En un post en el blog del equipo de Internet Explorer hacen referencia a un documento sobre seguridad en el que indican (en la sección 5) entre otras los posibles ataques que se pueden producir con este protocolo. Gran parte de la comunidad de desarrolladores no está de acuerdo y han contestado a estas reservas que tiene el equipo de IE, como se hace eco este post de ajaxian, blog muy activo dedicado al mundo del desarrollo de lado cliente.
En resumen
CORS es una gran iniciativa para librarnos a los programadores de las dificultades actuales para hacer llamdas entre dominios y, al mismo tiempo, conseguir seguridad para los usuarios. Los principales navegadores en sus últimas versiones soportan el último borrador del protocolo, con la notable excepción de IE8, y tampoco Opera, aunque en este caso dado su baja cuota de usuarios no supone demasiado problema.
Conviene que tratemos de usarlo en nuestros desarrollos de servicios para mejorar la seguridad de los usuarios y aunque no esté totalmente soportado aún.
Es de esperar que cuando el protocolo pase de la fase de borrador a versión definitiva todos los navegadores lo soporten (esperemos que también IE).
Con esto termino la serie de artículos sobre llamadas a servicios entre dominios. Espero que te haya resultado útil, y recuerda que si quieres formarte en desarrollo Web o en cualquier tecnología de Microsoft, en campusMVP encontrarás los mejores cursos on-line tutelados por conocidos MVP.
Las otras partes de esta serie:
· Parte 1: JSONP: llamadas AJAX a servidores remotos
· Parte 2: JSONP (II): Soporte desde ASP.NET AJAX 4.0
· Parte 3: JSONP (yIII): Cuestiones de seguridad y ASP.NET rompiendo la compatibilidad en 3.5
Para saber más: JavaScript profesional para desarrolladores y diseñadores web