Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SignalR cross-domain connections with self-hosting and authentication

Tags:

cors

owin

signalr

I have a CORS problem when self-hosting SignalR with OWIN, which only happens when I try to enable authentication.

The error I get in my web browser is:

XMLHttpRequest cannot load http://.../signalr/negotiate?[snip] Origin ... is not allowed by Access-Control-Allow-Origin

This only happens if I enable authentication in my self-hosted server using the approach in this answer:

public void Configuration(IAppBuilder app)
{
  var listener = (HttpListener)app.Properties[typeof(HttpListener).FullName];
  listener.AuthenticationSchemes = AuthenticationSchemes.Ntlm; 

  app.MapHubs(new HubConfiguration { EnableCrossDomain = true });
} 

If I comment out the AuthenticationSchemes line then CORS works (and I've checked everything in these instructions). I get the same problem if I use other authentication schemes than NTLM.

Using Fiddler to examine what's going on, without authentication enabled I see the necessary CORS headers coming back from the server:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: [my server]

However once I enable authentication I get a 401 response which is missing these headers. All the requests have the necessary Origin header.

Having examined the SignalR source code it looks like the headers are being set, but presumably with authentication enabled the HttpListener is sending the initial 401 response without hitting this code.

So I think my question is: How do I get the HttpListener to include an Access-Control-Allow-Origin header in its negotiation of authentication protocols?

like image 458
Matthew Richards Avatar asked Jul 05 '13 09:07

Matthew Richards


2 Answers

I have gotten NTLM authentication to work with cross domain signalR self-hosted in OWIN by allowing the preflight requests anonymous access.

What one needs to do is create a delegate for choosing the authentication scheme which looks for the preflight request headers, and allows these through anonymously. All other requests will use NTLM.

public void Configuration(IAppBuilder appBuilder)
{
    var listener = (HttpListener)appBuilder.Properties[typeof(HttpListener).FullName];
    listener.AuthenticationSchemeSelectorDelegate += AuthenticationSchemeSelectorDelegate;
}

private AuthenticationSchemes AuthenticationSchemeSelectorDelegate(HttpListenerRequest httpRequest)
{
    if (httpRequest.Headers.Get("Access-Control-Request-Method")!=null) 
        return AuthenticationSchemes.Anonymous;
    else 
        return AuthenticationSchemes.Ntlm;
}
like image 159
FXbeckers Avatar answered Oct 19 '22 15:10

FXbeckers


I presume you're using Chrome, which very unhelpfully tells you that these headers are missing and that this is the problem, when actually you have probably just forgot to set your XMLHttpRequest's withCredentials property to true.

If you're using jQuery you can do this for all requests with:

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
  options.xhrFields = { withCredentials: true };
});

You also need to do the right thing with OPTIONS requests as in the other answer.

like image 25
Alastair Maw Avatar answered Oct 19 '22 15:10

Alastair Maw