Misery with CORS

Author: Gábor Dolla
11 July 2013

OData is taking off, large systems start to support it including SAP Netweaver Gateway, SAP HANA, Microsoft CRM, Sharepoint, etc., you can see the growing list of systems supporting oData at http://www.odata.org/ecosystem/#3.

OData is a powerful protocol, you can think of it as a datasource, similar to ODBC/JDBC. You can put logic to the server side and call it from the client, much like calling a stored procedure in a database but also you can put the logic into the client and manipulate the data over oData. This latter use case makes oData really flexible, you can create a client in your favourite language using your favourite platform as long as your client speaks oData.

 

Let’s suppose that your company uses SAP for HR and it’s accessible using oData. Your task is to create a small html widget which displays the user’s timesheet and this widget will be included into various internal web pages, intranet portals.  So you develop this tiny little code, with JayData is really easy, just a few lines of code and markup. Then you try to use it, and BANG!, it does not work! After some debugging you find out that it’s because of CORS and your backend does not support CORS.

 

So, what is CORS ? See this: http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

“Cross-origin resource sharing (CORS) is a mechanism that allows Javascript on a web page to make XMLHttpRequests to another domain, not the domain the Javascript originated from. Such cross-domain requests would otherwise be forbidden by web browsers, per the same origin security policy.“

 

It means, that you put your widget to the web server where your web page is loaded from and then your code tries to access your backend which is on a different host. Your browser first asks your backend whether it’s ok or not and your backend must be able to answer this question. If your backend does not support CORS or denies the CORS request then the browser will not send requests to it. It meant to be a security measure.

 

As the deadline to make your widget to work is getting closer and closer you’re getting desperate to solve this problem. So what are the remedies ?

 

1. Put your widget or at least a part of it to the backend host and there will be no CORS issues at all. It worked for us with Microsoft CRM well but in many cases it is simple not feasible to serve your content from the backend.

2. Proxy the oData requests with the web server where your widget is, so there will be no CORS issues again. It requires support from the web server which is not feasible in many cases, too.

3. Create an oData proxy which will proxy only the oData requests and deals with CORS.

 

I’ll show you how to create an oData proxy with NGINX but it can be done with other tools like Apache, IIS, nodejs, etc.

 

So what exactly this proxy server does ?

1. adds the necessary headers to every responses (the Access-Control-Allow-* headers)

2. replies for every OPTIONS requests instead of forwarding it

 

If you have an ubuntu server with nginx just add a file under /etc/nginx/sites-enabled/ with the following content and restart nginx:

 

server {
    listen <YOUR PORT>;
    location / {
        if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Allow-Methods' 'OPTIONS,GET,HEAD,POST,MERGE,PATCH,DELETE,PUT';
                add_header 'Access-Control-Allow-Headers' 'X-PINGOTHER,Content-Type,MaxDataServiceVersion,DataServiceVersion,Authorization,X-Requested-With,If-Modified-Since';
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Content-Type' 'text/plain; charset=UTF-8';
                add_header 'Content-Length' 0;
                return 204;
        }

        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'OPTIONS,GET,HEAD,POST,MERGE,PATCH,DELETE,PUT';
        add_header 'Access-Control-Allow-Headers' 'X-PINGOTHER,Content-Type,MaxDataServiceVersion,DataServiceVersion,Authorization,X-Requested-With,If-Modified-Since';
        proxy_set_header Host $host;
        proxy_pass <URL OF YOUR REAL BACKEND SERVICE>;

    }

}

(I modified the config that I found here: https://gist.github.com/michiel/1064640)

 

Then in your code use the proxy server as the URL to the oData service instead of the real URL.

Share this

Leave a comment

comments powered by Disqus