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?
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:
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With