I set about making use of the new hub authorization feature in SignalR 1.0 today. It was a bit difficult to obtain answers about what it actually does and how it works, so I studied the revision that introduced this feature, wrote some test code of my own, and thought I would post my findings. This applies to the current version of SignalR as of this post, which is 1.0 RC1.
Some important Hub Authorization bullet-points:
Authorization to connect to a hub shouldn’t be confused with authorizing the underlying persistent connection. This means if a client (I happen to use the .net one) connects to two hubs with
// Create a proxy to the chat service var hub1 = hubConnection.CreateHubProxy("HubTypeOne"); var hub2 = hubConnection.CreateHubProxy("HubTypeTwo"); // Start the connection .Start().Wait(); hubConnection
and both HubTypeOne and HubTypeTwo do not authorize the hub connection, the underlying transport connection still stays open. This does make sense if you think about it, for WebSocket transport at least, since being authorized to connect to a hub is not a prerequisite to being authorized to invoke methods on the hub. But, an important consequence of this is that hub authorizers offer no protection against random people/bots that want to open up zillions of TCP sessions to your server, or attempt other shenanigans. I’ll have another post discussing how to authenticate all requests to SignalR before they even hit SignalR code soon - I’ve done it today, but have to write up the post…
Code samples using the Authorize attribute are in the SignalR source at samples/SignalR.Hosting.AspNet.Samples/Hubs/Auth/. See also the hub authorization feature introduction in David Fowler’s blog.
If you don’t implement any SignalR interfaces and just use the [Authorize] attribute, then the actual authorization logic that runs (code from Microsoft.AspNet.SignalR.Hubs.AuthorizeAttribute) is
private bool UserAuthorized(IPrincipal user) { if (!user.Identity.IsAuthenticated) { return false; } if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) { return false; } if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) { return false; } return true; }
where
- user is the System.Security.Principal.IPrincipal associated with the connection or hub invocation. If you know what what this is great, otherwise do what I did and write your own authorizers that tie into your app’s user management (see below.)
- _usersSplit and _rolesSplit correspond to the attribute’s Users and Roles properties, allowing you to taylor the authorization to specific usernames or roles:
[Authorize(Roles="Admin, Poweruser")] public class MySuperHub : Hub
There’s a shortcut extension method to apply authorization to all hubs (any authenticated user, can’t specify usernames/roles):
GlobalHost.HubPipeline.RequireAuthentication();
This should be called in your application startup code before creating your hub routing (MapHubs()).
Implementing your own authorizers
If you don’t like the UserAuthorized method that is the heart of the [Authorize] attribute, you can write your own authorizers. To do this, create a class that implements at least one of Microsoft.AspNet.SignalR.Hubs.IAuthorizeHubConnection or Microsoft.AspNet.SignalR.Hubs.IAuthorizeHubMethodInvocation. The parameters to these interface’s methods are very sensible and provide all sorts of information you might want in making an authorization decision - hub, method, user, cookies, and many others. If you want to apply your authorizer to hubs or hub methods by decorating them in code, you’ll of course need to subclass Attribute too. Here’s a class declaration that does all three of these things to get you started:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class CustomAuthorizeAttribute : Attribute, IAuthorizeHubConnection, IAuthorizeHubMethodInvocation
Applying your own authorizers to all hubs and method invocations
var globalAuthorizer = new CustomAuthorizeAttribute();
.HubPipeline.AddModule(new AuthorizeModule(globalAuthorizer, globalAuthorizer)); GlobalHost
Again, this should be called in your application startup code before creating your hub routing (MapHubs()
).