eworldproblems
  • Home
  • About
  • Awesome Ideas That Somebody Else Already Thought Of
  • Perl defects
  • Books & Resources
Follow

Posts in category SignalR

Securing SignalR to your site’s users



As you know if you read my last post on hub authorization, when a new hub connection is created, the transport connection stays open whether or not the hub connection(s) are authorized. In my case, I am using SignalR in a carefully controlled, secure authenticated environment, and as a common-sense security measure, I wanted to just close down any connections to SignalR from random clients that had no business connecting to my servers. Unfortunately, as of 1.0 RC1 there’s no built-in way to do this, but with some quick pointers from David Fowler I was able to put together something with Owin that causes my code to be run on every request first, before handing it off to SignalR. The result seemed like it might be useful enough to other people to be worthy of a blog post.

Disclaimers: This code was written for SignalR 1.0 RC1 and the version of owin that was around at that time. A visitor has posted some updates in the comments on this post’s page for SignalR 1.1.3. This applies to SignalR on top of IIS; I don’t know how it would be different if you’re hosting in some other way. This applies to requests that are routed to SignalR hubs (hub transport connections, method invocations, hubs auto-javascript.) You may need to do a similar thing for Persistent Connections too if you use them. Also, most of the calls and callbacks to and from Owin are just what “seemed to work,” but that doesn’t mean this is the best or most correct possible way to do it. We’ll just have to wait for Owin to be documented for that.

Step 1: Ditch RouteTable.Routes.MapHubs();

You should recognize this call from your application startup code (if you added SignalR via nuget, it’s in App_Start/RegisterHubs.cs). Internally, MapHubs tells Owin to route requests with certain urls right into SignalR code. This isn’t how we want to route. What we want to do is route them to our code first, where we’ll decide if we want to let the request through to SignalR or close it. So, we have to do do some routing calls ourselves:

// in addition to the usings you already have,
using Microsoft.AspNet.SignalR.SystemWeb.Infrastructure;
using Microsoft.Owin.Host.SystemWeb;
using Owin; // actually defined in a SignalR assembly and probably going to change soon...

public static void Start()
{
	// RouteTable.Routes.MapHubs(); not doing this anymore
	MapHubsWithSecurityInspector(RouteTable.Routes, "~/signalr", GlobalHost.DependencyResolver);
}

private static RouteBase MapHubsWithSecurityInspector(RouteCollection routes, string url, IDependencyResolver resolver)
{
	var existing = routes["signalr.hubs"];
	if (existing != null)
	{
		routes.Remove(existing);
	}

	var routeUrl = url.TrimStart('~').TrimStart('/');

	var locator = new Lazy(() => new BuildManagerAssemblyLocator());
	resolver.Register(typeof(IAssemblyLocator), () => locator.Value);

	return routes.MapOwinRoute("signalr.hubs", routeUrl, map => OwinSetup(map, resolver));
}

private static void OwinSetup(IAppBuilder builder, IDependencyResolver resolver)
{
	builder.Use(typeof(MySecurityInspectionHandler));

	builder.MapHubs(resolver);
}

Step 2: Write MySecurityInspectionHandler

After you’ve replaced MapHubs with the above, Owin will need you to define a MySecurityInspectionHandler class, because it will try to call it on every incoming request to routeUrl. The signature of MySecurityInspectionHandler is a bit magical, as the constructor and callback Owin actually looks for isn’t expressed anywhere in docs or as an interface or anything. Here’s what seems to work:

using System.Threading.Tasks;

// u might want to put the rest  in your app's namespace...
using AppFunc = Func<IDictionary<string, object>, Task>;

public class MySecurityInspectionHandler
{
	private AppFunc _app;

	public MySecurityInspectionHandler(AppFunc app)
	{
		_app = app;
	}

	public Task Invoke(IDictionary<string, object> environment)
	{
		// Finally, here's where we can examine the incoming request and allow or reject it
		if(RequestHasRightCookiesOrSomething(environment))
		{
			// continue processing the request
			return _app.Invoke(environment);
		} else {
			return StopRequestProcessing(environment);
		}
	}

	private Task StopRequestProcessing(IDictionary<string, object> environment)
	{
		environment.Add(ResponseStatusCode, 403);

		object responseStreamObj;
		environment.TryGetValue(ResponseBody, out responseStreamObj);
		Stream responseStream = (Stream)responseStreamObj;

		var streamWriter = new StreamWriter(responseStream);
		streamWriter.Write("403 Forbidden.");
		streamWriter.Close();

		var tcs = new TaskCompletionSource<bool>();
		tcs.SetResult(false);
		return tcs.Task;
	}
}

Then all you have to do is implement your actual logic to detect if this is a request from an authorized source or not, in a RequestHasRightCookiesOrSomething method. Just about everything you could ever want to know about the incoming request can be found in that environment IDictionary, unfortunately as objects with string keys that Intellisense couldn’t help you with. When you get to this stage just shove a breakpoint in and take a look at the Keys / Values inside environment to find what you need.

Step 3: Test

Visit some urls to your SignalR hubs manually in a browser, both with and without the expected authentication features sent along. When you do not send the authentication information, you should get a 403 forbidden back from URLs like

  • http://localhost/myproj/signalr
  • http://localhost/myproj/signalr/hubs
  • http://localhost/myproj/signalr/anything_else

When you do send authentication, /signalr visited manually currently produces an unknown transport exception, and /signalr/hubs produces the auto-javascript.

Hope this helps. Happy routing!

Tagged Security

SignalR Hub Authorization



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
    hubConnection.Start().Wait();

    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();
GlobalHost.HubPipeline.AddModule(new AuthorizeModule(globalAuthorizer, globalAuthorizer));

Again, this should be called in your application startup code before creating your hub routing (MapHubs()).

Recent Posts

  • Reset connection rate limit in pfSense
  • Connecting to University of Minnesota VPN with Ubuntu / NetworkManager native client
  • Running nodes against multiple puppetmasters as an upgrade strategy
  • The easiest way to (re)start MySQL replication
  • Keeping up on one’s OpenSSL cipher configurations without being a fulltime sysadmin

Categories

  • Computing tips
    • Big Storage @ Home
    • Linux
  • dev
    • devops
    • Drupal
    • lang
      • HTML
      • JavaScript
      • PHP
    • SignalR
  • Product Reviews
  • Uncategorized

Tags

Apache iframe malware performance Security SignalR YWZmaWQ9MDUyODg=

Archives

  • June 2018
  • January 2018
  • August 2017
  • January 2017
  • December 2016
  • November 2016
  • July 2016
  • February 2016
  • January 2016
  • September 2015
  • March 2015
  • February 2015
  • November 2014
  • August 2014
  • July 2014
  • April 2014
  • February 2014
  • January 2014
  • October 2013
  • August 2013
  • June 2013
  • January 2013
  • December 2012
  • November 2012
  • September 2012
  • August 2012
  • July 2012

Blogroll

  • A Ph.D doing DevOps (and lots else)
  • gavinj.net – interesting dev blog
  • Louwrentius.com – zfs@home with 4x the budget, other goodies
  • Me on github
  • My old edulogon.com blog
  • My old GSOC blog
  • My wife started baking a lot
  • Now it's official, my wife is a foodie

Meta

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org

EvoLve theme by Theme4Press  •  Powered by WordPress eworldproblems