SharePoint 2013 – Integration Challenges – #4 Same Origin Policy & Authentication – CORS

Hi,

I’m currently involved in integrating SharePoint with IBM Connections and I’m having a lot of fun trying to figure out all the possibilities. Since these integration considerations are not specific to SharePoint/IBM Connections, I’ll blog a series of posts which will be rather short or rather long according to the topic I’m focusing on.
In this post, I’m going to focus on CORS (Cross Origin Resource Sharing) and SharePoint 2013 considering that one way to consume SharePoint data from non Microsoft products is to leverage the SharePoint 2013 REST APIs. Microsoft solutions could make use of the CSOM but it’s not available when working with Java. Moreover, CORS is bi-directional while the CSOM in combination or not with the App Model is meant to consume SharePoint data from remote but not the other way around. Moreover, the App Model is also most suited when remaining in the Microsoft stack. I know that you can build provider-hosted apps with any technology including JAVA but it’s probably not the easiest path. So, I’ll take the angle of a remote application (say java) that tries to consume SharePoint’s REST API. But the same kind of technique can be used if you wish to work the opposite way (from SharePoint to a Remote App exposing some REST APIs)
If you haven’t read my previous posts of this integration challenges serie (click the Integration tag), you’d better do it to have a full picture of the possibilities. I’m not going to explain what’s the same-origin policy anymore.

CORS recap

In a nutshell, CORS is a communication between a browser and a server based on specific HTTP headers. The below schema shows a basic CORS conversation between a browser and a server:

cors.png

where:

  • 1) The preflight options request is sent depending on several factors such as the VERB being used (GET,POST,PUT,DELETE) and some custom headers emitted by the client request. Also, this preflight request can be cached using the max-age attribute.
  • 2) The server *should* return a 200 response including the expected CORS http headers.
  • 3) According to the headers returned by the server in the response, the browser will perform or not the actual cross-domain request
  • 4) The response from the remote domain

As this schema shows, this looks quite simple. On the server-side you can control which origin is allowed and which methods. So, you could for instance decide that GET and POST are allowed but not PUT and DELETE.
You could allow some specific HTTP Headers or not etc..

Ok for the theory but what about real world?

It turns out that at the time of writing, all the browsers are not yet compatible with CORS nor all the servers such as IIS if the targeted domain requires to be authenticated which is usually the case in SharePoint.
Because of that, preflight requests get a 401 (Unauthorized) answer from the server while it expects a 200. Therefore, for browsers that respect the CORS specification, if they receive anything else but 200, they should not perform the actual cross-domain request.Moreover, the HTTP request headers that should be part of a server response will not appear by magic, so it requires some extra configuration at the web server level.

Which approach to choose?

There are several approaches you can use in order to get things working with IIS and therefore, with SharePoint. A good blog post on that topic can be found at http://evolpin.wordpress.com/2012/10/12/the-cors/. I have tested his approach and it’s indeed working fine. In two words, in case you don’t want to read his whole post, the approach consists in :

    • Adding the necessary HTTP response headers Access-Control-Allow-Headers, Access-Control-Allow-Methods and Access-Control-Allow-Origin at IIS level. In a SharePoint context, you can add those headers for a given web app using the IIS console
    • Develop a HTTP module to work around the authentication problem regarding the preflight requests
public class CORSPreflightModule : IHttpModule
{
    private const string OPTIONSHEADER = "OPTIONS";
    private const string ORIGINHEADER = "ORIGIN";
    private const string ALLOWEDORIGIN = "http://....";
    void IHttpModule.Dispose()
    {

    }

    void IHttpModule.Init(HttpApplication context)
    {
        context.PreSendRequestHeaders += (sender, e) =>
        {
            var response = context.Response;

            if (context.Request.HttpMethod.ToUpperInvariant() == OPTIONSHEADER.ToUpperInvariant() &&
                context.Request.Headers[ORIGINHEADER] == ALLOWEDORIGIN)
            {
                response.StatusCode = (int)HttpStatusCode.OK;
            }
        };

    }
}

An alternative approach using a Reverse Proxy

So, instead of repeating what the other blogger said, let me propose an alternative approach. The HTTP module one is not my prefered option since it creates an unnecessary overhead at SharePoint level for every single request even those that are not concerned by CORS. As an alternative, if you have a Reverse Proxy at your disposal, you can achieve the same goal by placing the CORS logic at another level.
In development environments, you usually don’t benefit from Enterprise reverse proxies. Therefore, let’s see with our best friend Fiddler how to get the same results and get CORS up & running with SharePoint on your dev box. With this method, IIS is left untouched and no custom HTTP Module is required and if the final solution in the other environments use reverse-proxies, you’ll be closer to the reality.

  • Open Fiddler, Rules, Customize Rules
  • Once in the file, find the OnBeforeResponse function and add the following
    static function SetCORSResponseHeaders(oSession: Session,origin: String)
    {
     oSession.oResponse.headers.Add("Access-Control-Allow-Headers","Content-Type,Authorization");
     oSession.oResponse.headers.Add("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,MERGE");
     oSession.oResponse.headers.Add("Access-Control-Allow-Origin",origin);
     oSession.oResponse.headers.Add("Access-Control-Allow-Credentials",true);
    }
    
    static function OnBeforeResponse(oSession: Session) {
    var origin:String="http://urlofallowedorigin"; //do not include a ending / character
    //in this case, we know we're in a CORS context since the origin header was sent
    if(oSession.oRequest.headers["Origin"] != '')
    {
      SetCORSResponseHeaders(oSession,origin);
     //In case we receive a preflight request
     if(oSession.oRequest.headers.HTTPMethod=='OPTIONS'
      && oSession.oRequest.headers["Origin"]==origin)
     {
      oSession.oResponse.headers.HTTPResponseCode  = 200;
      oSession.oResponse.headers.HTTPResponseStatus = "200 OK";
     }
    }
    

    In this case, I’m checking whether the ORIGIN request header is present or not which indicates if a CORS operation is ongoing. If it’s indeed a CORS related request, I inject the HTTP headers required by CORS (+credentials which I’ll detail later). Then, I check whether the method is OPTIONS which identifies a preflight request. If it’s a prefight, I return the status code 200 (instead of default IIS 401). So, with that basic rule, I’m covering both preflight requests and actual requests targeting the remote domain. I could have also checked the current uri to make sure that I want to allow a given ORIGIN to contact a given target of course. Also, since the reverse proxy, in this case, Fiddler, sits between the browser and the server, you can easily configure rules the other way around by inverting the source & the target.

Client request and authentication

$.support.cors=true;
$.ajax({
    url:"remote domain/_api/web/lists/getbytitle('Documents')/itemCount",
    xhrFields: {withCredentials: true},
     headers:{ "Accept": "application/json; odata=verbose" },
     contentType: "application/json; odata=verbose"
  }).done(function(data, textStatus){
     if (data.d) {
       $('#doccount').text("Number of documents in documents:"+data.d.ItemCount);
     }
  }).fail(function(){
      $('#doccount').text("failed");
  });

So, the query is very similar to a usual query. I just tell jQuery that I’m using CORS and I asked it to forward the credentials via the withCredentials attribute. For that to work fine, you need the server to send back the Access-Control-Allow-Credentials response header with the value true. If you have never authenticated to the remote domain at the time the AJAX query is sent, you’re likely going to be prompted (with Chrome/Firefox). With IE, you won’t be prompted if the target URL is in the Trusted/Intranet zone. This statement is valid for Claims – NTLM. If you use FBA, the browser will forward the Cookie if it already exists, meaning you have already authenticated before the AJAX request takes place otherwise, you’ll get a 403 which you could handle in code and redirect to the login page…Of course, as I stated in another post, you can also use Fiddler (or your production proxy) to inject authentication information on the fly but I’m not going to repeat those steps in this post.

Environment I used for my tests

  • SharePoint 2013
  • IIS 7.5.7600.16385
  • Chrome 32.0.1700.102
  • Firefox 24.0
  • Internet Explorer 10
  • jquery1-9-1.min.js
  • Authentication : Claims – NTML & FBA

Pros & Cons of CORS in combination with SharePoint 2013

Let’s start with the Cons:

  • At the time of writing, it’s not supported by IE 8/9 (at least not conform to the CORS specification) which is already quite a strong limitation in many enterprises.
  • Even Firefox & Chrome have some different ways of handling the specification but they comply with most of it
  • As you could see, it requires some efforts server-side to get up & running

No, let’s see the Pros:

  • When it will be fully supported and a common framework, it’s a very good way to control who can talk to who and to have a granular control over what the caller can do (read? write?…)
  • With the web/mobile apps expanding a lot, CORS will be a common architecture very soon

But why using CORS via a reverse proxy?

If you read one of my posts about the same-origin policy http://www.silver-it.com/node/150 you might wonder why not just applying the technique it described via the reverse proxy instead of creating CORS specific rules. I think that the technique described in that post ends up in lying to the browser while if you place CORS specific rules at the reverse proxy level, you don’t lie to the browser and the same-origin policy is still taking place. Morever, you realy make use of an emerging standard, the only difference is that you place the configuration one step above.
However, lying to the browser comes with one advantage : it’s not browser/server dependent and will always work while CORS still has some limitations regarding portability. So, depending on your environment, you might chose what best suits you.
Happy Coding!

Advertisements

About Stephane Eyskens

Office 365, Azure PaaS and SharePoint platform expert
This entry was posted in Javascript and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s