Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Fixing AJAX: XMLHttpRequest Considered Harmful
by Jason Levitt | Pages: 1, 2

Apache Proxy

Using an Apache proxy is the easiest and cleanest way of getting around these restrictions. However, it requires that you have access to the main Apache httpd.conf file as well as have Apache's mod_proxy extension loaded. Apache proxy directives are not allowed in local .htaccess files so this method is not a good choice for developers using shared hosting services.

# Pass the call from http://www.yourserver.com/call to http://api.local.yahoo.com
ProxyPass    /call/    http://api.local.yahoo.com/
# Handle any redirects that yahoo might respond with
ProxyPassReverse    /call/    http://api.local.yahoo.com/

Another way to do this is to use Apache's mod_rewrite using the passthru directive:

RewriteEngine on 
RewriteRule ^/call/(.*)$ http://api.local.yahoo.com/$1 [P]

(Note: this rewrite rule may be broken across more than one line in your browser; but mod_rewrite rules won't work that way, so be careful.)

The Apache proxy approach is clean and simple. Consider a call to the Yahoo Geocode REST Web service:

http://api.local.yahoo.com/MapsService/V1/geocode?appid=YahooDemo&location=78704

With either of the Apache proxy examples functioning, your AJAX application can make a call to:

http://www.yourserver.com/call/MapsService/V1/geocode?appid=YahooDemo&location=78704

and the request will be seamlessly forwarded, and the results returned, to your AJAX application.

Script Tag Hack, or On-Demand JavaScript

The script tag hack is a more complicated approach that involves dynamically generating an HTML script tag and using the src attribute of the tag to make the request. It never makes an XMLHttpRequest, but it's worth looking at because it provides a fairly standard way of making web service requests from within an AJAX application. The code I talk about below is adapted from Darryl Lyons' blog posting on the subject.

The HTML script tag can only return JavaScript. So, to make this approach work, we need to modify the application proxy above to return JavaScript. Two str_replace statements are used to encode any single quotes or newlines that may be in the XML -- they will cause JavaScript scripts to break. Finally, the Content-Type header is changed from text/xml to text/javascript. I'll call the new proxy proxy_script.php (here, the proxy returns XML, but we could also return JSON or other data encodings as well). The XML is placed into a JavaScript global variable -- in this case I've given it the imaginative name xml:

...
...
// Make the call
$xml = curl_exec($session); 

// encode the returned XML as a Javascript variable
$xml =str_replace("'", "'", $xml); 
$xml =str_replace("\n", "", $xml); 
$xml = 'var xml = \''.$xml.'\';';
// The web service returns javascript
header("Content-Type: text/javascript");
echo $xml;
...
...

To make a call, you dynamically create a script tag in your browser's DOM and then point the src attribute at the proxy. To demonstrate this as simply as possible, I created a short web page:

<body>
<a href="javascript:getLocations()">Click this link</a> to dynamically create a script tag and make 
a web service call. A button will appear below after the call has finished</p>
<br/><br/>
<div id="locationData"></div>
</body>

Clicking on the link above will call the function getLocations() which will dynamically create a script tag and retrieve a web service. The function getLocations() calls the function getDataFromServer():

function getLocations() {
     getDataFromServer("ScriptTagID","http://localhost/proxy_script.php");
}</p>

The function getDataFromServer takes as arguments the name of the script tag that will be dynamically generated (and later destroyed) and the prefix of the URL to be fetched. getDataFromServer() does all the real work. The script tag in IE 5 and 6 can fetch data asynchronously using a proprietary mechanism described here. To encapsulate that behavior, I check for the presence of an IE browser using the same definition function as Sarissa, renamed to BROWSER_IS_IE. For all other web browsers, the script tag essentially fetches data synchronously, though it's possible to attach pseudo-asynchronous properties to it (this is left as an excercise for the reader).

After the data is fetched via the newly created script tag, the callback function, putXMLhere(), is called, and the fetched data is available.

<script>
 var callback = "putXMLhere();";
 
function getDataFromServer(id, url) {

    // Fetch the element pointed to by the id. If it exists, we destroy it so we can create a new one.
    oScript = document.getElementById(id);

    // Point at the script tag, if it exists
    var head = document.getElementsByTagName("head").item(0);
     // Destroy the tag, if it exists
    if (oScript) {
    // Destory object
    head.removeChild(oScript);
    }<p>    // Create the new script tag
    oScript = document.createElement("script");

    // Setup the src attribute of the script tag
    sendPath = encodeURIComponent("/MapsService/V1/geocode?appid=YahooDemo&location=78704");
    wholeurl = url + "?path=" + sendPath;
    oScript.setAttribute("src", wholeurl);

    // Set the id attribute of the script tag
    oScript.setAttribute("id",id);

    // Create the new script tag which causes the proxy to be called
    head.appendChild(oScript);
    // Asynchronous script tag properties -- a proprietary IE "feature"
    if (BROWSER_IS_IE) {
        if  (oScript.readyState == "loaded") {
        eval(callback);
        oScript.onreadystatechange = null;
       } else {
        oScript.onreadystatechange = CheckAgain;
       }
    // All other web browsers just do the callback function
    } else {
       eval(callback);
    }
}
 
// Used by IE to handle state changes
function CheckAgain() {
  if (oScript.readyState == "loaded") {
      eval(callback);
      oScript.onreadystatechange = null;
    } 
}</p>// Once the script tag has loaded the data, it's available in the global Javascript variable "xml" which was sent from the proxy.
 function putXMLhere() {
     ohandle = document.getElementById("locationData");
     ohandle.innerHTML = ohandle.innerHTML + "<form><input type='button' value='View XML' onClick='alert(xml); return true;' /></form>";
  }
</script>

Further Plumbing

All of these approaches will provide a seamless, cross-browser AJAX experience for your users while they access third-party web services. But always check with your friendly security administrator before deploying them.

Yes, it's all been done before. These three sites were my major sources of inspiration (or plagiarism): :-)

Darryl Lyons' page on dynamic script tag usage was the inspiration for my investigation.

Premshree Pillai's discussion of the Apache proxy got me looking at that territory.

Think about Cross-Domain Mediation using portlets -- Cross-Domain Mediation is a nice name for "proxy."

Example code for this article: scripthack.zip


Comment on this articleShare your hacks with the rest of us!
(* You must be a
member of XML.com to use this feature.)
Comment on this Article


Titles Only Titles Only Newest First
  • False security and useful workarounds
    2008-04-30 00:09:00 SamGoody [Reply]

    As long as there are workarounds, no matter how convoluted they are, the "bad guys" will use them. Thus, something like this "security" feature is completely useless - you will find phished and unfairly promoted sites using these hacks - but it severely impedes the work of those that really are looking for security.


    Case-in-point: Until Ajax came along, almost all login pages used an ssl encrypted connection. Now that ajax is in vogue, many, many e-commerce sites send the data completely unsecured. They have no choice: the site is http, and this "security" stupidity will not allow a connection to a https page.


    Or even more commonly, the whole site doesn't work, as the designer didn't realize that the browser will not allow a request to "http://www.example.com from http://example.com


    Or people use the hacks mentioned above without fully understanding the technology, and expose their entire server to attack.


    All while doing nothing at all to actually add even the remotest level of security, since the hacks exists for those that are motivated to use it.

  • Cross-domain scripting with dynamic script tags
    2006-04-25 20:17:41 lindseykuper [Reply]

    Jason, thanks for the article. I've cited it in an article I just wrote that uses a technique similar to the third one you described. It's here: http://www.rockstargirl.org/sandbox/bidynodes/. Thanks again!

  • ContextAgnosticXmlHttpRequest Proposal
    2006-02-27 14:16:09 cth [Reply]

    A while back ago, i gave an overview of the very valid security concerns behind restricting xmlhttprequests to the same host, and offered an informal proposal for a new object with looser security settings, but with a few restrictions: ContextAgnosticXmlHttpRequuest (http://chrisholland.blogspot.com/2005/03/contextagnosticxmlhttprequest-informal.html) . The idea is to retain security while allowing for a more straightforward and appropriate way for sites to syndicate content from each-other.

  • Solution Required for Problem in "Ajax in Struts-based Web Application"
    2006-02-09 03:35:12 bibroy [Reply]

    A highlevel view of the pseudo-code is follows:


    AjaxFunctionOuter (){


    ....some code specific to AjaxFunctionOuter


    for-loop(...){


    AjaxFunctionInner ();


    }
    }



    AjaxFunctionOuter works fine in all the cases both in terms of request-dispatch and response-processing


    The problem is related to AjaxFunctionInner which is described in the following paragraphs:

    Case I: [when the req.open(.., ..., true) for AjaxFunctionInner ]


    AjaxFunctionInner does work in terms of request-dispatch to struts-action for all the iteration in the for-loop.
    AjaxFunctionInner does NOT work in terms of response-processing except for the last iteration in the for-loop.

    This case is same for IE and mozilla-based browsers(such as netscape, firefox, mozilla).



    Case II: [when the req.open(.., ..., false) for AjaxFunctionInner ]

    A)
    For IE:
    AjaxFunctionInner does work in terms of request-dispatch to struts-action for all the iteration in the for-loop.
    AjaxFunctionInner does work in terms of response-processing for all iterations in the for-loop.



    B)
    For mozilla-based browsers:


    AjaxFunctionInner does NOT work in terms of request-dispatch to struts-action for all the iteration in the for-loop.
    AjaxFunctionInner does NOT work in terms of response-processing for all the iteration in the for-loop.[THIS IS OBVIOUS]


    Now what appears from the above 2 cases is that for multiple-sequential ajax requests (such as those that might occur in a
    for loop) there appears to be some issue related to intercepting the response and subsequent processing.

    Case II (A) do not offer much hope since it defeats the very purpose of "A" of AJAX.


    Now I have been through quite a handful of articles/FAQ related to AJAX, but to no avail.
    I have tried Javascript closures, but that too do not seem to work.
    Note: I am using POST (in call to open method) for all cases since I am doing form submission.



    Let me know if anyone has a solution to offer in view the above perspective and also whether I am missing something.







  • Using Flash for cross-domain request
    2006-02-02 18:26:07 Julien Couvreur [Reply]

    I wrote a Flash object to allow cross-domain HTTP requests from javascript: http://blog.monstuff.com/archives/000277.html


    It's the same Flash object that I was using for client-side storage, for writing offline AJAX applications.

  • synchronous script tag hack?
    2005-12-13 17:21:39 dr_burrito [Reply]

    This is a very useful and insightful article, though I think there's one important mistake in the Script Tag Hack section: "For all other web browsers, the script tag essentially fetches data synchronously." Ideally, I'd like to do just that -- include a script synchronously in Firefox and other browsers. But in my testing, the script inclusion is asynchronous. I can only safely proceed with the new script if I use an event handler or polling.


    Is there a way to do truly synchronous on-demand JS?


  • Security is relative
    2005-11-10 19:01:59 fleegix [Reply]

    What's commonsensical security in one environment is an irritating limitation in another. This article shows several good ways to implement cross-domain requests with XMLHttpRequest.


    This is an issue that more people are now having to deal with, as more people begin developing Web apps the AJAX way. In fact, coincidentally, I posted in my Weblog on this exact same topic (http://fleegix.org/articles/2005/11/07/cross-domain-ajax-requests) a couple of days ago.

  • Wow, you guys are finally getting back to Java
    2005-11-10 18:56:36 wonderman [Reply]

    The interesting thing about AJAX is how it is finally comming around to the power of Java on the page. The suggested security changes are exactly what the Java security model for applets has used since early on. This keeps script kiddie from connecting to local, already authorized locations and containing sensitive information.


    Without this protection they can collect local network information and transfer it back to remote hosts!


    Lookout, the next version of internet exploitations are on the way...


  • Security is critical
    2005-11-10 14:25:54 SubbuAllamaraju [Reply]

    Not sure why the author treats the security constraints a limitations. We don't want to a new breed of security holes running wild on the web.

  • Make $$$ fast with XML
    2005-11-10 00:13:57 otto [Reply]

    The title for this article is a bad choice: Ajax ain't broken, and XmlHttpRequest is Not Considered Harmful by anyone.


    What's up next? "Make $$$ fast with XML"?

    • Conventions for humorous article titles (Make $$$ fast with XML)
      2005-11-14 02:37:09 Damian Cugley [Reply]

      I agree. I'd like to suggest that variations on 'goto considered harmful' only be used in articles about a programming paradigm that is as ubiquitous and conventionally approved of as 'goto' was before Dijkstra's article, and which the article claims is so harmful that right-thinking people should avoid it. Joel Spolsky's essay arguing that exceptions a bad language feature (http://www.joelonsoftware.com/items/2003/10/13.html) is a case in point.


      If on the other hand you are discussing some trendy new feature that everyone says is SO simple but which has a few tricky aspects, then 'XMLHttpRequest gotchas' might be more appropriate, or possibly some variation on 'What your mother never told you about XMLHTTPRequest'.