CORS with Sencha Touch

A while back I was working on the mobile version of techup.ch which you can find on m.techup.ch. The web application is built with Sencha Touch. I did not want to deploy to m.techup.ch yet, which is where I ran into issues with the same origin policy.

In order to prevent web applications gaining access to things they should not have access to there is a same origin policy, which means that AJAX requests can only be made to the same host and port that the website is hosted on. This prevents CSRF attacks, as an attacker cannot make your browser do arbitrary actions on remote websites.

Our Sencha Touch app however relies on a JSON API, that is served from techup.ch. Since the app itself is not hosted on that domain (yet), it cannot access the API.

A workaround: JSONP

While AJAX is restricted by the same origin policy, script tags are not. It is possible to include script tags to a remote site, which is often used to embed widgets from sites like Facebook or Twitter. It can also be abused to fetch data from a remote domain.

This works by adding a script-tag proxy pointing to the API, including a callback parameter in the query string, for example:

1
http://techup.ch/api/events/upcoming.json?callback=processData

The API will detect the callback parameter and pad the response with a javascript call to the data it returns.

Conventional Reponse

{
    "events": [
        {
            "id": 361,
            "name": "Linalis LPI 201"
        }
    ]
}

JSONP Response

processData({
    "events": [
        {
            "id": 361,
            "name": "Linalis LPI 201"
        }
    ]
});

And this is what the “P” in JSONP is. JSON with padding.

Of course the processData function has to exist. In fact, jQuery’s AJAX function has built in support for JSONP. If you have callback=? in the requested URL, it will automatically use JSONP for the request. Sencha Touch also supports this through Ext.data.ScriptTagProxy.

But this is more a hack than a real solution. Enter CORS.

Cross-Origin Resource Sharing

CORS is a W3C Working Draft which describes an extension to HTTP for allowing browsers to issue AJAX request across domains. The server serving this request can send additional headers notifying the client of these additional permissions.

The only header that needs to be set is:

Access-Control-Allow-Origin: *

This will allow AJAX requests from any domain. Instead of the wildcard, you could also specify allowed domains explicitly. Only set the wildcard if it is safe to do so. You need to be aware that any site could get any user to make a request to those resources on your site that have this header, which can very easily lead to CSRF attacks.

Now, Sencha Touch (as well as many other AJAX libraries) will also send a X-Requested-With header, allowing the server to detect that the request was sent through JavaScript. CORS forces the server to specify which headers can be sent by the client. That is done as folllows:

Access-Control-Allow-Headers: x-requested-with

This is a comma-delimited list, so you can add more headers if needed.

If you want to allow other HTTP methods than GET, you’ll have to specify that explicitly with yet another header:

Access-Control-Request-Method: GET,POST

Authentication

By default the browser will not send any cookies with CORS requests. You can however set the Access-Control-Allow-Credentials header with a value of

1
true

, which will allow cookies to be sent. In this case you may however want to explicitly define a range of allowed origin domains.

Alternatively you can handle authentication yourself. In this case the user will have to provide username and password to the app, which will then make a request to the API. The API will respond with an authentication token, which can be used in subsequent authenticated requests to the API. This way you do not have to rely on cookies. You can store this token in localStorage, so you don’t loose it on page reloads.

Browser support

A recent article describes the browser support as follows:

  • Webkit browsers: good
  • Gecko browsers: good
  • Trident browsers (Internet Explorer 8+): good with some gotchas
  • Opera: very sucky a.k.a. non existent

Since we are targeting mobile here, which is in this case pretty much webkit only, we can just use it.

Conclusion

CORS is a nice solution for remote API access from mobile web applications.

Check out enable-cors.org.

November 3, 2011 by Igor Wiedler in Development // Tags: , , 13 Comments

13 Responses to CORS with Sencha Touch

  1. Raphael says:

    Thanks for this introduction. I think you’re describing CORS pretty well. I do, however, have one gotcha to add. Reading your post, I asked myself how those last three response headers (Access-Control-Allow-Headers, Access-Control-Request-Method and Access-Control-Allow-Credentials) are supposed to work, given that they influence what’s being sent in the request, when, in actuality, the request has already been sent (since the response has to be a response to some request). So I searched for these headers and found out that they’re not sent in response to the actual request but to an OPTIONS request called a pre-flight. Maybe you could mention that in your post…

  2. Igor Wiedler says:

    Yep, for non-safe requests, the browser does a pre-flight check, to make sure it does not destroy anything. The result of the OPTIONS request is cached in the browser.

    As soon as you need to modify the state of the server, you’ll need to restrict Access-Control-Allow-Origin, or be very careful about how you authenticate your user (disable cookie-based authentication).

    But sure, these things are worth mentioning. Thanks for pointing that out!

  3. Amit says:

    i am also trying to show user tweets with this url url: ‘http://twitter.com/status/user_timeline/’+user+’.json?count=10&callback=processData’, but it doesnt work…looking around for more help

  4. Vassilis says:

    Is this working for Chrome (17)? Whenever I set the Origin header in an Ext.Ajax.request, Chrome says “refused to set unsafe header Origin”. This happens in the preflight check.

    Every site or blog I’ve checked says that Chrome supports CORS. I haven’t managed to get it working though…

  5. Igor Wiedler says:

    @Amit: That URL is using JSONP, here’s an example with jQuery:

    
    
    1
    $.get('https://twitter.com/status/user_timeline/igorwesome.json?count=10&callback=?', function (data) { console.log(data[0]) }, 'jsonp');

    @Vassilis: The headers have to be set on the response. You will need to do that on the server, not the client. I hope that fixes it!

    • Vassilis says:

      That’s what I’m doing: a) I’ve set Access-Control-Allow-Origin, Access-Control-Allow-Methods and Access-Control-Allow-Headers on the server. b) I’ve set the Origin header in my Ajax request. Still, chrome refuses to set the Origin: refused to set unsafe header Origin. I’m scratching my head for a few hours now.

      Have you got a working example using Ext.Ajax?

      • Igor Wiedler says:

        The origin is sent from the client automatically, you do not have to set any additional headers yourself. You’re getting the error because it is not possible to set that header from within JavaScript.

  6. emil says:

    Opera support should not be neglected tho, I heard they have 230m+ devices running opera.

  7. Ian says:

    Can anyone here tell me what happens if i decide to package my web based app (delivered from a webserver A and accesing a rest api on webserver B, where B has CORS enabled=allows ajax requests from A) as a native app using e.g. the sencha packaging tools (SDK Tools) ??? can that then only work when i say * for all origins on B ? am just not sure what origin is then assumed by B…apart from that…great blog! this blog post has already helped me a lot…although it did lead to the above question :)

  8. zy says:

    Hi, My ST2 app is hitting a web service that provides a json response and it sets a cookie. My subsequent calls to this server needs to send back the cookie. To enable this, I have set:

    1
    2
    Access-Control-Allow-Credentials : true
    Access-Control-Allow-Origin: *

    And my ajax request is as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
        Ext.Ajax.request({
        url: 'http://someURL',
        withCredentials: true,
        useDefaultXhrHeader: false,
         method:'GET',
         callback: function(options, result, response) {
         console.log(result);
         }
    });

    I get the following error:

    1
    Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.

    I assume If I give my local domain instead of ““, it will work. But why does not it work for ‘‘? Anything that I need to add or remove ?

    • zy says:

      For some reason, the * was removed.

      I assume If I give my local domain instead of *, it will work. But why does not it work for * ? Anything that I need to add or remove ?

    • I think the problem is that allowing cookies to be passed while allowing calls from every domain leads to high chances of CSRF attacks. Restricting it to your domain means you remain in control.