Question

SignalR scales fabulously using Azure Web Roles. However, when I use a self-hosted OWIN project inside an Azure Worker Role, SignalR will begin to exhibit problems when multiple instances are added. For the record, my project uses Redis for the backplane.

When increasing the Azure Worker Role instances beyond one, client connections will randomly fail with an error "The ConnectionId is in the incorrect format". I believe this is caused when the negotiation for a single client spans multiple servers due to load-balancing; I do not believe the multiple servers participating in the negotiation can decrypt the data (DPAPI under the covers?).

I tried setting the < machineKey /> validationKey and decryptionKey in app.config but this doesn't appear to make a difference; the problem remains. Again, the project will work fine as a Web Role (IIS), but not a Worker Role (OWIN self-host).

Presuming this is an issue with DpapiDataProtectionProvider, how can I ensure the provider renders the same encrypt/decrypt result across multiple servers/instances?

Solution

The default protection provider used by SignalR (DpapiDataProtectionProvider) does not appear to support Azure Worker Role scaleout. By rolling my own sample provider I was able to scale SignalR/OWIN/Azure Worker and not receive the random 400 HTTP/"The ConnectionId is in the incorrect format". Keep in mind the below sample will not secure/protect the tokens.

public class ExampleDataProvider : IDataProtector
{
    public byte[] Protect(byte[] userData)
    {
        Trace.TraceInformation("Protect called: " + Convert.ToBase64String(userData));
        return userData;
    }

    public byte[] Unprotect(byte[] protectedData)
    {
        Trace.TraceInformation("Unprotect called: " + Convert.ToBase64String(protectedData));
        return protectedData;
    }
}

public class ExampleProtectionProvider : IDataProtectionProvider
{
    public IDataProtector Create(params string[] purposes)
    {
        Trace.TraceInformation("Example Protection Provider Created");
        return new ExampleDataProvider();
    }
}

Injecting the custom provider during Owin startup:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.SetDataProtectionProvider(new ExampleProtectionProvider());

        GlobalHost.DependencyResolver.UseRedis(new RedisScaleoutConfiguration("0.0.0.0", 0, "", "Example"));

        app.MapSignalR(new HubConfiguration
        {
            EnableDetailedErrors = true
        });

        app.UseWelcomePage("/");
    }
}
Was it helpful?

Solution

SignalR uses the IDataProtectionProvider from IAppBuilder.Properties["security.DataProtectionProvider"] if it's not null.

You can replace this with your own IDataProtectionProvider in Startup.Configuration before you call MapSignalR.

By providing you own IDataProtectionProvider, you can ensure that every work role uses the same key to protect/unprotect.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top