In a previous post I described how to use ADAL JS with Azure AD role-based authorization. This works fine when you're securing a Web API or MVC backend. However, what about SignalR hubs? In short, SignalR enables real-time communication between a client and a web server. The client can call methods on a so-called hub and the server can push messages to clients (all clients, a specific group of clients, only the calling client, etc). SignalR is an abstraction over a number of transport methods. Preferably WebSockets but if either browser or server does not support this, a number of fallback protocols exist: server-sent events, forever frame or Ajax long polling.
Suppose you have a HTML/JS front-end and a back-end that exposes a SignalR hub. The official documentation suggests that you should integrate SignalR into the existing authentication structure of the application. So you authenticate to your application, inform the client of the relevant authentication information (username and roles for example) and use this information in calls back to the SignalR hub. This seems a bit backward if you ask me.
I already have ADAL JS on the client (browser). ADAL JS provides the client with a JWT token that is stored in local or session storage. So the first question is: how do we configure SignalR on the client to send the token along with requests to the SignalR hub on the server. That's the topic of the current post. In the next post, the server-side of things will be handled.
On the client I use AngularJS and jQuery so I also use the ADAL JS Angular wrapper. This makes initialization easier and allows you to configure ADAL JS on routes to trigger authentication. So the code samples assume that you use AngularJS and ADAL AngularJS. The client-side SignalR library allows for easy extension of the SignalR connect requests from the client to the server, as shown in the following example:
// Id of the client application that must be registered in Azure AD. var clientId = "12345678-abcd-dcba-0987-fedcba12345678"; var NotificationService = (function () { // Inject adalAuthenticationService into AngularJS service. function NotificationService(adalAuthenticationService) { this.adalAuthenticationService = adalAuthenticationService; } NotificationService.prototype.init = function () { var self = this; $.connection.logging = true; $.connection.hub.logging = true; $.connection.hub.transportConnectTimeout = 10000; // Add JWT token to SignalR requests. $.connection.hub.qs = { token: function () { // Obtain token from ADAL JS cache. var jwtToken = self.adalAuthenticationService.getCachedToken(clientId); return (typeof jwtToken === "undefined" || jwtToken === null) ? "" : jwtToken; } }; $.connection.hub.start(); }; NotificationService.$inject = ["adalAuthenticationService"]; return NotificationService; })(); appMod.service("notificationService", NotificationService); appMod.run(NotificationService);
The magic happens on the $.connection.hub.qs
property. Parameters specified there are sent on subsequent SignalR negotiate, connect and start requests1. So we'd expect a token
parameter in our case. When recording network traffic between client and server we can see this is actually happening:
And here are the details of the connect request. You can see that subsequent ping requests also contain the ADAL JS JWT token:
So that's it for the client side of things. In the next post we switch to the server and see how to intercept the token and use it to create a principal that can be used for authorization.
Notes
- There's an excellent explanation of what happens on the wire with SignalR here.