Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using ajaxSetup beforeSend for Basic Auth is breaking SignalR connection

I have a WebApi secured with Basic Auth which is applied to the entire Api using a AuthorizationFilterAttribute. I also have SignalR Hubs sitting on several of my Api Controllers.

Alongside this I have a web page which makes use of my WebApi. The web page is mostly written in Backbone, so in order to make calls to my secured WebApi, I have added the following jquery

$.ajaxSetup({
    beforeSend: function (jqXHR, settings) {
        jqXHR.setRequestHeader('Authorization', 'Basic ' + Token);
        return true;
    }
});

This works for communicating with my Api Controllers, but adding the above code has broken the connection to my SignalR Hubs, specifically:

XMLHttpRequest cannot load http://localhost:50000/signalr/negotiate?_=1366795855194. 
Request header field Authorization is not allowed by Access-Control-Allow-Headers. 

Removing the jqXHR.setRequestHeader() line restores my SignalR Hub connection but breaks the Api calls.

Given the above, I could do something hacky and only set the request header if the request being made isn't to /signalr but that just feels dirty...

Is there a cleaner way around this?

Am I just doing something silly? Has anyone else ran in to this?

like image 714
Stevie Avatar asked Apr 24 '13 10:04

Stevie


2 Answers

What I didn't mention before is that I have a DelegatingHandler which sends back the correct Headers to any request coming in to my WebApi. This works perfectly for any requests to my WebApi but I wrongly assumed that this would also apply to SignalR requests.

As SignalR relies on several different transport methods, it doesn't seem reasonable to assume I have access to Authorization headers in the first place - they're not a requirement of all WebSockets implementations for example (see here)

My current solution has been to make use SignalR's HubPipeline (detailed here). Using this, I believe I can pass the Basic Auth credentials in a query string and write a separate module for handling Authorization for the SignalR requests:


Passing the Query string

$.connection.hub.qs = "auth=" + MyBase64EncodedAuthString;

The Filter

public class SignalrBasicAuthFilterAttribute: Attribute, IAuthorizeHubConnection {

    public bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request) {
      var authString = request.QueryString["auth"];

      // ... parse, authorize, etc ...

      return true;
    }

}

Registering the Filter

var globalAuthorizer = new SignalrBasicAuthFilterAttribute();
GlobalHost.HubPipeline.AddModule(new AuthorizeModule(globalAuthorizer, globalAuthorizer));

Additionally...

Note that because it's not a reliable assumption to send an Authorization header with SignalR requests, for the aforementioned reasons, I am still filtering my $.ajaxSetup to only affect non-SignalR requests:

$.ajaxSetup({
    beforeSend: function (jqXHR, settings) {
        if (settings.url.indexOf("/signalr") == -1)
            jqXHR.setRequestHeader('Authorization', 'Basic ' + Token);
        return true;
    }
});

In doing this, I'm leaving SignalrBasicAuthFilterAttribute class to take on full responsibility for Authorizing SignalR requests.


Further Reading:

  • http://weblogs.asp.net/davidfowler/archive/2012/11/11/microsoft-asp-net-signalr.aspx
  • http://eworldproblems.mbaynton.com/2012/12/signalr-hub-authorization/
  • SignalR authentication within a Controller?
like image 82
Stevie Avatar answered Nov 15 '22 10:11

Stevie


I think the real solution for the issue will be to make sure that "Authorization" is part of the allowed Headers(Access-Control-Allow-Headers) returned from the signalR response for "negotiate" request.

You could register the header in your web.config just like this possibility.

<httpProtocol>
  <customHeaders>
    <add name="Access-Control-Allow-Headers" value="Authorization" />
  </customHeaders>
</httpProtocol>
like image 39
rpgmaker Avatar answered Nov 15 '22 11:11

rpgmaker