Cross origin SignalR connection stops after negotiate

asp.net-web-api, cors, signalr, signalr-hub

Solution

Solved it. I had changed the `map.UseCors(CorsOptions.AllowAll)` to pass in a `CorsPolicy` object instead, and set `SupportsCredentials` to false, having read elsewhere that `Access-Control-Allow-Origin: *` is incompatible with `access-control-allow-credentials: true`.

private static readonly Lazy<CorsOptions> SignalrCorsOptions = new Lazy<CorsOptions>(() =>
{
    return new CorsOptions
    {
        PolicyProvider = new CorsPolicyProvider
        {
            PolicyResolver = context =>
            {
                var policy = new CorsPolicy();
                policy.AllowAnyOrigin = true;
                policy.AllowAnyMethod = true;
                policy.AllowAnyHeader = true;
                policy.SupportsCredentials = false;
                return Task.FromResult(policy);
            }
        }
    };
});

public void Configuration(IAppBuilder app)
{
    app.Map("/signalr", map =>
    {
        map.UseCors(SignalrCorsOptions.Value);
        map.RunSignalR(new HubConfiguration());
     });
     //now start the WebAPI app
     GlobalConfiguration.Configure(WebApiConfig.Register);
}

Setting `SupportCredentials` to true results in the `Access-Control-Allow-Origin` header being rewritten with the actual origin (not `*`) and `access-control-allow-credentials: true` in the response.

And now it works.

Problem

I have an MVC 5 app serving up views, and a Web API 2 app as the service layer (.NET 4.5). The Web API app uses SignalR 2.1.2 to return progress as it's processing POSTs to the service API. The two are deployed to different domains, so I've set up cross origin support as per the asp.net tutorial article. ``` [assembly: OwinStartup(typeof (Startup))] namespace MyApp.Service { public class Startup { public void Configuration(IAppBuilder app) { app.Map("/signalr", map => { //worry about locking it down to specific origin later map.UseCors(CorsOptions.AllowAll); map.RunSignalR(new HubConfiguration()); }); //now start the WebAPI app GlobalConfiguration.Configure(WebApiConfig.Register); } } } ``` WebApiConfig.cs also contains its own CORS declaration. ``` namespace MyApp.Service { public static class WebApiConfig { public static void Register(HttpConfiguration config) { //controller invocations will come from the MVC project which is deployed to a //different domain, so must enable cross origin resource sharing config.EnableCors(); // Web API routes config.MapHttpAttributeRoutes(); //Snip other controller dependency initialisation } } } ``` I've defined a simple hub class with no server-side API (it's only to allow the server to push to the clients, not for the clients to call into). ``` namespace MyApp.Service.Hubs { [HubName("testresult")] public class TestResultHub : Hub { } } ``` Since I'm going cross-domain AND the hub is not exposing any server side API, I'm not bothering to use a generated JS proxy. The relevant bits of the JS that set up the signalr hub connection is: (remember this is being served up from the MVC app, which does not have any signalr support (except jquery-signalr-{version}.js of course)) ``` function TestScenarioHandler(signalrHubUrl) { var self = this; //Snip irrelevant bits (mostly Knockout initialisation) self.signalrConnectionId = ko.observable(); var hubConnection = $.hubConnection(signalrHubUrl, { useDefaultPath: false }); var hubProxy = hubConnection.createHubProxy("testresult"); hubProxy.on("progress", function(value) { console.log("Hooray! Got a new value from the server: " + value); }); hubConnection.start() .done(function() { self.signalrConnectionId(hubConnection.id); console.log("Connected to signalr hub with connection id " + hubConnection.id); }) .fail(function() { console.log("Failed to connect to signalr hub at " + hubConnection.url); }); } ``` Going cross-origin like this, Firefox network traffic shows (and I've confirmed Chrome shows the same thing) a GET to `http://****service.azurewebsites.net/signalr/negotiate?clientProtocol=1.5&connectionData=[{"name":"testresult"}]&_=1424419288550` Notice that the `name` matches the value of the `HubName` attribute on my hub class. This GET returns HTTP 200, the response gives me a JSON payload containing a `ConnectionId`, `ConnectionToken`, and a bunch of other fields that suggests everything's ok. The HTTP response also has the `Access-Control-Allow-Origin:` header set to the domain that the GET originated from. All up it looks good, except that's where the traffic stops. But the JS console prints `"Failed to connect to signalr hub at http://****service.azurewebsites.net/signalr"` To verify I'm not doing anything too stupid, I've added signalr support and a basic hub to the MVC app (so no cross origin required), and changed the `$.hubConnection()` and `hubConnection.createProxy()` calls accordingly. When I do that, browser traffic shows the same `/signalr/negotiate?...` GET (obviously not cross origin any more), but then also GETs to `/signalr/connect?...` and `/signalr/start?...`. The JS console also prints a success message. So in summary; - CORS is enabled on the service layer, and the signalr `/negotiate` GET returns 200, what appears to be a valid connection id, and the expected `Access-Control-Allow-Origin:` header. This suggests to me that the server-side CORS support is behaving itself correctly, but the signalr connection does not succeed. - When I reconfigure so the signalr connection is NOT cross origin, everything works as expected. WTF am I missing or doing wrong?! Some conflict between `HttpConfiguration.EnableCors()` and `IAppBuilder.UseCors(CorsOption)` perhaps?

Original source