If you are a ASP.NET developer, SignalR is a great library to enable real time communication between your server and client code. SignalR ships with both server and client libraries and I’ll assume that you are somewhat familiar with the two, otherwise you can read up on them here.
The reason behind why I write this blog post is to tackle the difficulties of authentication towards a SignalR application using a token, in this case a JSON Web Token. The problem is that the SignalR JavaScript library does not provide support for passing headers in requests, headers in which these tokens are normally sent. More specifically via the Authorization header and this is also where the JWT middleware will look for it.
I’ll walk you through how to install both SignalR and the JwtBearerAuthentication middleware as well as writing our own “querystring-to-authorization header” translation middleware. In the end we should have the means of authenticate ourselves to a SignalR endpoint by passing a token as a part of the query string instead of as a header.
Installing SignalR
Let’s get started by adding support for SignalR in our ASP.NET Core 1.0 application by installing (adding to our project.json file) the following NuGet packages from the aspnet master MyGet feed.
"Microsoft.AspNet.SignalR.Server": "3.0.0-rc1-final"
Once these are installed successfully we simply add and configure SignalR in our Startup class. I’ve omitted everything else for brevity.
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); } public void Configure(IApplicationBuilder app) { app.UseSignalR(); } }
This should install and configure our application to be a SignalR endpoint so lets get down to business, authentication!
Authenticate against SignalR
[Authorize] public class MyGreatHub : Hub { }
This is great and all, but what if you want to authenticate using Json Web Tokens (i.e. JWT)? Typically one would send a JWT in the Authorization header using the Bearer-schema and the content of the authorization header would be in the form “Bearer <token>”. This is all fine and dandy and is supported by ASP.NET Core 1.0 by downloading the following NuGet packages (at the time of writing I am using the rc1-final versions).
"Microsoft.AspNet.Authentication": "1.0.0-rc1-final", "Microsoft.AspNet.Authentication.JwtBearer": "1.0.0-rc1-final"
And configuring them by adding the authentication service and use the JwtBearerAuthentication middleware provided by the JwtBearer NuGet package.
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(); services.AddSignalR(); } public void Configure(IApplicationBuilder app) { app.UseJwtBearerAuthentication(options => { options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; options.Authority = "Url to your identity provider"; //For example, a tenant url if you are using Azure AD options.Audience = "Your audience"; //For example, an Azure AD AppIdUri }); app.UseSignalR(); } }
Pay extra attention to the options that we need to provide when we add our JwtBearerAuthentication. Audience is the indented recipient of the token, as explained here. If you are using Azure AD we would specify the App ID URI of the application that requested the token as Audience. Authority is a URL to your identity provider, aka Authority. If you are using Azure AD this would typically point to your tenant (https://login.microsoftonline.com/{tenantId}).
Pass our JWT in the query string
Now, one would then assume that simply sending the token in the authorization header from the client when connecting to the hub would authenticate the user and yes, that would work. However, the SignalR JavaScript client library provided by Microsoft does not include a way to modify or add headers sent to the server. It does however provide a way to append a query string to each request and that is something that we want to take advantage of.
Lets go ahead and add our token to the query string being sent in each SignalR request. I’ve removed all other code for brevity but a complete example how to use the SignalR JavaScript library can be found here. All we need to do is to get a hold on the reference of our hub connection and assign our query string to the “qs” property of the connection. This property is a key-value-pair where the key is the name of the header and the corresponding value is the header value.
var connection = $.hubConnection(); connection.qs = { 'authorization': '<token>' }
Make the SignalR endpoint accept our query string
Following the previous steps would enable us to pass the token from the client to the server, but it would not work out of the box since the JwtBearer authentication mechanism looks for the authorization header and not the authorization query string value.
Let’s add a middleware that reads our query string and adds an bearer token authorization header using the provided query string. Provided that we add this middleware before the Jwt middleware in the pipline, we could “trick” it to believe that the client passed the header, that is pretty neat.
Let’s begin by updating our existing Configure method. I’ve only replaced everything that has to do with Jwt with our new middleware, “UserJwtAuthentication”, which I am going to implement further down.
public void Configure(IApplicationBuilder app) { app.UseJwtAuthentication(); app.UseSignalR(); }
Let’s start with re-adding our Jwt code from our previous step in our new middleware.
public static void UseJwtAuthentication(this IApplicationBuilder app) { app.UseJwtBearerAuthentication(options => { options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; options.Authority = "Url to your identity provider"; //For example, a tenant url if you are using Azure AD options.Audience = "Your audience"; //For example, an Azure AppIdUri }); }
As of now, we have only wrapped the JwtBearerAuthentication middleware in our own, and this won’t help much. We need to add our authorization header to make JwtBearerAuthentication work. Now, it is very important that we to that before JwtBearerAuthentication. Remember that ASP.NET vNext is built up by a pipeline of middlewares, each middleware is executed in the same order as they are declared.
I will just use the quick inline version of a middleware which takes the current context and a delegate to the next step in the pipeline. We will start by looking at the request, specifically after the existence of a authorization header since we do not want to override a real header being sent by a client. If such a header exist we simply do nothing but invoking the next middleware in the pipeline, otherwise we are free to add it. Adding a header is a straight forward task, we can simply add a new key value pair to the Headers collection of the Request. Let us check if we have a query string parameter named “authorization”, and if we do we extract its value (i.e the JWT) and use that value when adding the Authorization header. Please note the “Bearer” prefix which is needed.
public static void UseJwtAuthentication(this IApplicationBuilder app) { app.Use(async (context, next) => { if (string.IsNullOrWhiteSpace(context.Request.Headers["Authorization"])) { if (context.Request.QueryString.HasValue) { var token = context.Request.QueryString.Value .Split('&') .SingleOrDefault(x => x.Contains("authorization"))?.Split('=')[1]; if (!string.IsNullOrWhiteSpace(token)) { context.Request.Headers.Add("Authorization", new[] {$"Bearer {token}"}); } } } await next.Invoke(); }); app.UseJwtBearerAuthentication(options => { options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; options.Authority = "Url to your identity provider"; //For example, a tenant url if you are using Azure AD options.Audience = "Your audience"; //For example, an Azure AppIdUri }); }
That should be it, now we should be able to authenticate against a SignalR endpoint using a JSON Web Token.
Summary
- SignalR does not have any special authentication mechanism built in, it is using the standard ASP.NET authentication.
- JWT is typically sent in the Authorization header of a request
- The SignalR JavaScript client library does not include the means to send headers in the requests, it does however allow you to pass a query string
- If we pass the token in the query string we can write a middleware that adds a authorization header with the token as its value. This must be done before the Jwt middleware in the pipeline
- Be aware of the “Bearer <token>” format