In my experience, SignalR has proven to be a great way to push events from server to the clients, both web-based, as well as mobile apps. In this post I would like to describe how SignalR handles scaling out to multiple hosts. I will also try to resolve a problem that occurs, when you are trying to do that with OWIN self-hosted SignalR.

Scaling out

(you can find code related to this article here)

Below is a typical scenario that you will have for scaling out: multiple hosts behind a load balancer.

Scaled out SignalR hosts

Given the nature of HTTP and provided you want to avoid sticky sessions, the user in most cases will connect to a random host. If you want to be able to generate a message on any of the hosts and send it to all your connected users, there has to be a way for the hosts to exchange information about sent messages. For this very purpose, SignalR has a concept of backplane. Backplane is a message bus that enables broadcasting messages between hosts.

There are multiple implementations of the service bus you can use, including: Redis, Azure Service Bus, SQL Server, RabbitMQ. Or you can implement your own.

Using backplane is dead easy, you just have to install a NuGet package with selected backplane implementation and do some configuration in code. Here's a sample for Redis:

var redisConfig = new RedisScaleoutConfiguration(
    "redis connection string here (compatible with StackExchange.Redis)",
    "name of you application here");


Protected data

But that's not all!

SignalR uses encryption to protect some data, like for example connection tokens. When hosted in IIS, it will encrypt with the machine key (the one in your machine.config or web.config). In this scenario it is enough to have the same machine key in the web.config on all of you hosts. Connection token encrypted on one host, can be decrypted on another. Some hosting environments, like Azure Web Apps, will do this for you automatically, if you choose to host your web app on more than one instance.

Unfortunately the situation is a bit different when you are self-hosting SignalR with OWIN on multiple machines. For example, you could have such a scenario with Service Fabric based application.

In self-hosted scenario, SignalR will use DPAPI to protect data. DPAPI will encrypt the data with either machine-scoped or user-scoped encryption keys. The former will not allow for decryption on another machine. The latter might, in theory, with roaming user profiles. But it would be a pain to configure in most cases. Also, it will decrypt on any machine the user can execute code on, so that may be a security problem.

The result of this is that you will get HTTP 400 trying to connect most of the time. This is because SignalR will make at least 2 requests at handshake and those will usually hit two different machines.

Here's the code from SignalR codebase, that is responsible for encryption configuration:

// Use the data protection provider from app builder and fallback to the
// Dpapi provider
IDataProtectionProvider provider = builder.GetDataProtectionProvider();
IProtectedData protectedData;

// If we're using DPAPI then fallback to the default protected data if running
// on mono since it doesn't support any of this
if (provider == null && MonoUtility.IsRunningMono)
    protectedData = new DefaultProtectedData();
    if (provider == null)
        provider = new DpapiDataProtectionProvider(instanceName);

    protectedData = new DataProtectionProviderProtectedData(provider);

resolver.Register(typeof(IProtectedData), () => protectedData);

Running on .NET, it will use an implementation of IDataProtectionProvider it will find in the OWIN configuration. And if it doesn't find one, it will default to DPAPI.

IDataProtectionProvider interface comes from Microsoft.Owin.Security package. DataProtectionProviderProtectedData is a SignalR's adapter, also implementing IProtectedData.

So how to deal with this problem? The answer should be obvious by now. Use a custom encryption key, and a custom implementation of IDataProtectionProvider.

PLEASE NOTE that cryptography related issues should be handled with care. Make sure you know what you are doing. You can introduce vulnerabilities into you software if you don't.

There is a Owin.Security.AesDataProtectorProvider package that contains AES based implementation of IDataProtectionProvider. Here's how to use it:

app.UseAesDataProtectorProvider("some password");

The encryption key will be derived from the password, so you have to make sure you have the same password on all hosts. This package uses SHA1 to derive password, however it would be better to use a function that is specialized in key derivation, like PBKDF2. For my purposes this is enough however, since in my case I don't handle any sensitive data and the API is public.

You can find sample code for the described scenario here.

So this is it. With this configuration you can successfully run SignalR in Service Fabric, or any other self-hosted scenario.

About the author:

Marcin Budny

Team lead of R&D at BT Skyrise. Works with Intelligent Transport Systems (ITS), where he faces problems in terms of equipment and software. He is passionate about the architecture of applications, cloudcomputing and IT systems quality. He constantly searches for novelties on software development. Marcin specializes in .NET technology, however he looks for inspiration in other languages and platforms.

Next Post Previous Post

blog comments powered by Disqus