In SharePoint 2013, we were recently working with integrating SignalR(+Owin) new version 2.2.1.0 for achieving one of the customer's requirements. 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 explain how to use SignalR and how to use CORS in SignalR. I am sure if you follow the 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 the internet have suggested to comment out this statement if web.config already has the “Access-Control-Allow-Origin” header. But in our case the web application address we wanted to have in the web.config was for the 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 DON’T WORK practically with “Access-Control-Allow-Origin” no matter what W3 or MicroSoft standards say.
- <add name="Access-Control-Allow-Origin" value="http://intra.abc.com, http://intra.pqr.com" />
- <add name="Access-Control-Allow-Origin" value="http://intra.abc.com http://intra.pqr.com" />
- <add name="Access-Control-Allow-Origin" value="*" />
Some posts on the 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. This 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 has worked like a charm so far for every cross domain request coming towards the 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 perfectly.