Wednesday 26 April 2017

“Access-Control-Allow-Origin” – A saviour for Cross Domain calls if used wisely!

In SharePoint 2013, We were recently working with integrating SignalR(+Owin) new version 2.2.1.0 for achieving one of the customers requirement. During the implementation, it invoked the need for having the cross domain connection.

In this article, I won’t go through details regarding SignalR implementation as it would diverge from the current topic. You may find some nice articles here which explains how to use SignalR and how to use CORS in SignalR. I am sure if you follow steps and articles diligently you won’t face any problem implementing this.

The Problem:

In our case we got below error in IE when we were trying to establish cross domain hub connection from client side using SignalR CORS:

SEC7128: Multiple Access-Control-Allow-Origin headers are not allowed for CORS response.


Before going further let’s first assume, we have 3 different intranet web applications in SharePoint 2013 environment and Signalr hub is configured on all these 3 applications.

Web application 1: http://intra.abc.com
Web application 2: http://intra.pqr.com
Web application 3: http://intra.xyz.com


During the investigation of the above error it was found that there was already an entry available for “Access-Control-Allow-Origin” custom header in web.config (3rd Web application) and it was like this

<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="http://intra.abc.com" />
<add name="Access-Control-Request-Method" value="POST,GET,OPTIONS,PUT,DELETE" />
<add name="Access-Control-Request-Headers" value="Content-Type, Authorization, Accept" />
<add name="Access-Control-Allow-Credentials" value="true" />


And hence SignalR code (placed on 2nd application) was failing from client side when it tried to establish a cross domain hub connection with 3rd application.


Why an error?

SignalR CORS code adds the required header and value for “Access-Control-Allow-Origin” in response headers and this was creating the duplicate entries in response (one from web.config and one from SignalR CORS code) and hence there was an error. Please keep this in mind, there must be only one entry of “Access-Control-Allow-Origin” in response headers.


Investigation

This is the statement from SignalR startup class which adds the allow origin entry to the response header:

map.UseCors(CorsOptions.AllowAll);

Some blogs on internet have suggested to comment out this statement if web.config already have the “Access-Control-Allow-Origin” header. But in our case the web application address we wanted to have in the web.config was for 2nd whereas it was for 1st application in config.

During troubleshooting, we found the real problem with this header “Access-Control-Allow-Origin”. This header accepts ONLY ONE ORIGIN value and it doesn’t accept multiple domain addresses added with space separated, comma separated or “*” at all.

These are the 3 cases which DOESN’T WORK practically with “Access-Control-Allow-Origin” no matter what W3 or MicroSoft standards say:



  • <add name="Access-Control-Allow-Origin" value="*" />


Some posts on internet said “*” works but if we have to use “*” then please keep "Access-Control-Allow-Credentials" as "false". But we tried that one as well and it doesn’t work.

So only working option remaining with “Access-Control-Allow-Origin” was using single domain address for origin.

In our case we were making calls/connections with 3rd application. Means we were required to have two domain addresses (2nd application for SignalR and 1st application for other old feature) to be registered in allow origin header but it seemed this is not possible from web.config of 3rd application. If we just keep one address then it will disable the other 
application feature.

Solution:

So to overcome the situation, after investigation and googling, we came up with one solution. The solution proved pretty helpful for both SignalR + Old feature.

Step 1: We decided to get the ORIGIN address dynamically instead of hardcoding it in 
web.config of 3rd application. So we just moved it to Global.asax file (in 3rd application) and 
then removed entries from web.config. We can may be filter coming requests in 
“Application_BeginRequest” event to allow only 1st and 2nd application.

Global.asax:

<%@ Assembly Name="Microsoft.SharePoint"%>
<%@ Application Language="C#" Inherits="Microsoft.SharePoint.ApplicationRuntime.SPHttpApplication" %>

<script runat="server">
     void Application_BeginRequest(object sender, EventArgs e) {       
                  Response.Headers.Remove("Access-Control-Allow-Origin");
Response.AddHeader("Access-Control-Allow-Origin", Request.UrlReferrer.GetLeftPart(UriPartial.Authority));

                  Response.Headers.Remove("Access-Control-Allow-Credentials");
                  Response.AddHeader("Access-Control-Allow-Credentials", "true");

                  Response.Headers.Remove("Access-Control-Allow-Methods");
                  Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
     }
</script>


Step 2: After this we commented out this statement from SignalR startup code which was adding a allow origin header. So now it will no longer add any allow origin header.

map.UseCors(CorsOptions.AllowAll);

This solution working like a charm so far for every cross domain request coming towards 3rd application (whether from 2nd application for SignalR OR from 1st application for old feature) and both features on 1st application and 2nd application worked perfect.


Happy Learning! J

No comments:

Post a Comment