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.
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-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="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 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.
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.
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