Question

I'm working on getting CORS working with SignalR 2.0.0 - I know there are plenty of SO posts out there about it and I've been looking through them all.

I encountered an issue while testing it with disabled web security in Chrome that hopefully someone can explain.

I have two separate MVC5 sites (separate solutions) running on IIS Express (different ports). One of them is the host server, and it has SignalR installed. The Startup.cs for the host looks like this:

public void Configuration(IAppBuilder app)
        {
            app.Map("/signalr", map =>
            {
                map.UseCors(CorsOptions.AllowAll);
                var hubConfiguration = new HubConfiguration
                {
                   // EnableJSONP = true                    
                   // EnableDetailedErrors = true                                     
                };
                map.RunSignalR(hubConfiguration);
            });
        }

I just have one hub on the host with one method. The other MVC5 site is the client, and it has the following JavaScript:

  • jquery-1.10.2.min.js
  • jquery.signalR-2.0.0.min.js
  • a reference to the ~/signalr/hubs JavaScript on the host MVC site

The JS that it calls is the following (pretty standard):

$.connection.hub.logging = true;
var chat = $.connection.chatHub;

    chat.client.broadcastMessage = function (name, message) {
        var encodedName = $('<div />').text(name).html();
        var encodedMsg = $('<div />').text(message).html();
        $('#discussion').append('<li><strong>' + encodedName
            + '</strong>:&nbsp;&nbsp;' + encodedMsg + '</li>');
    };
    $('#displayname').val(prompt('Enter your name:', ''));
    $('#message').focus();

    $.connection.hub.url = 'http://localhost:60601/signalr'; // <-- pointing to the host MVC5 site
    $.connection.hub.start().done(function () {
        console.log("Connected, transport = " + $.connection.hub.transport.name);
        $('#sendmessage').click(function () {
            chat.server.send($('#displayname').val(), $('#message').val());
            $('#message').val('').focus();
        });
    }).fail(function (data) {
        console.log('[Hub Start FAILED]' + data);
    });

EDIT TO POST LOGS

When I use the code from above, I get no errors on my host site. On the client site, I get these errors in the Chrome DevTools console:

[18:06:41 GMT-0600 (Central Standard Time)] SignalR: Auto detected cross domain url. 
[18:06:41 GMT-0600 (Central Standard Time)] SignalR: Client subscribed to hub 'chathub'.
[18:06:41 GMT-0600 (Central Standard Time)] SignalR: Negotiating with 'http://localhost:60601/signalr/negotiate?connectionData=%5B%7B%22name%22%3A%22chathub%22%7D%5D&clientProtocol=1.3'.
MLHttpRequest cannot load http://localhost:60601/signalr/negotiate?connectionData=%5B%7B%22name%22%3A%22chathub%22%7D%5D&clientProtocol=1.3&_=1386201997462. The 'Access-Control-Allow-Origin' header contains the invalid value 'http://localhost:50709, *'. Origin 'http://localhost:50709' is therefore not allowed access.
SignalR error: Error: Error during negotiation request.
[18:06:41 GMT-0600 (Central Standard Time)] SignalR: Stopping connection.

If I change the starting of the hub in my client's javascript by adding parameters, like so $.connection.hub.start({ jsonp: true, transport: 'webSockets' }) (if I don't specify webSockets as the transport then SignalR successfully uses longpolling transport), then I get the following error logs.

Client side (Chrome DevTools Console)

[18:06:41 GMT-0600 (Central Standard Time)] SignalR: Auto detected cross domain url. 
[18:06:41 GMT-0600 (Central Standard Time)] SignalR: Client subscribed to hub 'chathub'.
[18:12:16 GMT-0600 (Central Standard Time)] SignalR: Negotiating with 'http://localhost:60601/signalr/negotiate?connectionData=%5B%7B%22name%22%3A%22chathub%22%7D%5D&clientProtocol=1.3'
[18:12:17 GMT-0600 (Central Standard Time)] SignalR: Connecting to websocket endpoint 'ws://localhost:60601/signalr/connect?transport=webSockets&connectionToken=GzcGbVs35BV0AFtJ2%2BJQqEYlQfW6ejf6CJ8QI73zfG8OsonD0LIzDmtzNBWM6jSHiD0ptuStjmyOKTpszMFJOgVZoBLuYN08TYx1WUbdNqKDLRZ35Vozmk666%2F7x3rjgowmXhTjX0pVlx8nDY%2FWrpA%3D%3D&connectionData=%5B%7B%22name%22%3A%22chathub%22%7D%5D&tid=4'
[18:12:17 GMT-0600 (Central Standard Time)] SignalR: Websocket opened.
[18:12:22 GMT-0600 (Central Standard Time)] SignalR: webSockets timed out when trying to connect.
[18:12:22 GMT-0600 (Central Standard Time)] SignalR: Closing the Websocket.
SignalR error: Error: No transport could be initialized successfully. Try specifying a different transport or none at all for auto initialization.
[18:12:22 GMT-0600 (Central Standard Time)] SignalR: Stopping connection.
[18:12:22 GMT-0600 (Central Standard Time)] SignalR: Fired ajax abort async = true. 

and host side (in VS2013 Output window):

SignalR.Transports.TransportHeartBeat Information: 0 : Connection 3e856309-8396-481e-8d32-b4085a93985f is New.
SignalR.Transports.WebSocketTransport Information: 0 : CloseSocket(3e856309-8396-481e-8d32-b4085a93985f)
SignalR.Transports.TransportHeartBeat Verbose: 0 : 3e856309-8396-481e-8d32-b4085a93985f is dead
SignalR.Transports.WebSocketTransport Information: 0 : Abort(3e856309-8396-481e-8d32-b4085a93985f)
SignalR.Transports.TransportHeartBeat Information: 0 : Removing connection 3e856309-8396-481e-8d32-b4085a93985f
SignalR.Transports.WebSocketTransport Information: 0 : End(3e856309-8396-481e-8d32-b4085a93985f)
SignalR.Transports.WebSocketTransport Verbose: 0 : DrainWrites(3e856309-8396-481e-8d32-b4085a93985f)
SignalR.Transports.WebSocketTransport Information: 0 : CompleteRequest (3e856309-8396-481e-8d32-b4085a93985f)

The Issue

I start the host site in Chrome disabled web security mode and the client site normally in Chrome and I still get a CORS error and the connection is dropped.

I start the client site in Chrome disabled web security mode and the host site normally in Chrome and I encounter no CORS issues and the connection works great.

(it works great if both sites are running in Chrome disabled web security mode as well, but that isn't surprising)

The Question

I would expect to not encounter any CORS issues when the host has its security disabled and the client doesn't - not the other way around. Why does this happen?

Was it helpful?

Solution

The problem seems to be with your Access-Control-Allow-Origin header.

map.UseCors(CorsOptions.AllowAll);

The above line should cause every response to a SignalR CORS request to have the following header:

Access-Control-Allow-Origin: (request origin)

In your case, the (request origin) should be http://localhost:50709, but instead it is http://localhost:50709, * judging by the following log output:

[18:06:41 GMT-0600 (Central Standard Time)] SignalR: Negotiating with 'http://localhost:60601/signalr/negotiate?connectionData=%5B%7B%22name%22%3A%22chathub%22%7D%5D&clientProtocol=1.3'.
HTMLHttpRequest cannot load http://localhost:60601/signalr/negotiate?connectionData=%5B%7B%22name%22%3A%22chathub%22%7D%5D&clientProtocol=1.3&_=1386201997462.
The 'Access-Control-Allow-Origin' header contains the invalid value 'http://localhost:50709, *'. Origin 'http://localhost:50709' is therefore not allowed access.
SignalR error: Error: Error during negotiation request.

This is most likely caused by something similar to the following in your web.config:

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

Or perhapse something similar to the following in your middleware code:

Response.AppendHeader("Access-Control-Allow-Origin", "*");

Either way, since you are already using map.UseCors(CorsOptions.AllowAll); you should not be setting the Access-Control-Allow-Origin header elsewhere.

Note about JSONP

Lastly, I know you probably only did this for debugging purposes, but starting the connection like so: $.connection.hub.start({ jsonp: true, transport: 'webSockets' }) probably isn't the best idea.

With these settings, the negotiate request (and any long polling requests) will always be made with JSONP, not CORS. This might make sense when you can't get CORS to work, but if you can get CORS working you should use that instead and not enable JSONP since CORS allows for more fine-grained access control configuration.

If you set EnableJSONP to true on the server, you are allowing JS code running on any website to access your SignalR service no matter what you set your CorsOptions to.

If you decide to leave JSONP enabled on the server, there is no reason to specify jsonp: true in the configuration object you pass to hub.start since SignalR should fall back and try to use JSONP if CORS requests fail.

The same goes for specifying transport: 'webSockets'. By default, SignalR will try to use the WebSocket transport first if it's available.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top