Authorize Your Azure AD Users With SignalR

As with pretty much everything in the ADAL packages, this is something that seems like it should be pretty straight-forward, but isn’t.

Before we go on to SignalR, we need to have a look at how we access a Web API endpoint that requires authentication from JavaScript. If you’re already familiar with AD and bearer tokens, you can skip this bit.

It’s Easy in WebAPI

Okay, let’s say we have an OWIN app already set up to use Azure Active Directory for authorisation.

app.UseWindowsAzureActiveDirectoryBearerAuthentication(
    new WindowsAzureActiveDirectoryBearerAuthenticationOptions
    {
        Tenant = AppSettings["ida:Tenant"],
        TokenValidationParameters = new TokenValidationParameters
        {
            ValidAudience = AppSettings["ida:ClientId"]
        }
    });

This means that, in our controllers, we can do things like this:

[Authorize]
public abstract class UsersController : ApiController
{
    [HttpGet]
    public async Task<IHttpActionResult> GetUserName()
    {
        return Ok(ClaimsPrincipal.Current.Identity.Name);
    }

and only authenticated users will be able to hit that endpoint.

Including Authorize is enough to force users to authenticate to get through, and to populate ClaimsPrincipal.Current with the user credentials.

Connecting from JavaScript

Now we have our API endpoint, how do we access it from JavaScript?

Microsoft provide an Azure AD library for JS as part of the general Azure library. The example code only shows samples for using it with Angular. If you want to go down a less masochistic route and just use something like JQuery, then there’s also an annoyingly hard to find library of sample code for ADAL without using Angular.

The ADAL library provides a wrapper through which we can grab a bearer token to attach to any requests.

I don’t want to go into this in too much detail since there are two full libraries of code samples linked above, but we’ll end up with something like this:

namespace Security {
    var authContext;

    export function init(config) {
        const authConfig = {
            instance: config.instance,
            tenant: config.tenant,
            clientId: config.clientId,
            postLogoutRedirectUri: window.location.origin
        };

        authContext = new AuthenticationContext(authConfig);

        const isCallback = authContext.isCallback(window.location.hash);
        authContext.handleWindowCallback();

        if (isCallback && !authContext.getLoginError()) {
            window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
        }

        if (!authContext.getCachedUser()) {
            authContext.config.redirectUri = window.location.href;
            authContext.login();
            return false;
        }

        return true;
    }

    export function logOut() {
        if (authContext) {
            authContext.clearCache();
        }
    }

    export function acquireAuthToken() {
        var deferred = $.Deferred();
        authContext.acquireToken(authContext.config.clientId, (error, token) => {
            if (token) {
                deferred.resolve(token);
            } else {
                deferred.reject(error);
            }
        });

        return deferred;
    }
}

Once have have something like that set up, we can initialise then get bearer tokens from our client. Then we can add that token into our AJAX request and pass through to our Users API (from above).

// Auth config
if (!Security.init(config)) {
    // Stop app init if there is no user
    return;
}

Security.acquireAuthToken()
    .done((token) => {
        const jqSettings = {
            url: '/api/users/getusername',
            method: 'GET',
            beforeSend: (xhr) => {
                xhr.setRequestHeader('Authorization', `Bearer ${token}`);
            }
        };

        $.ajax(jqSettings)
            .done((username) => {
                console.log('username', username);
            });

So now that’s out of the way, how does that fit in with SignalR?

It’s Not Easy in SignalR

It’s not as simple to wrap up calls to a SinglaR hub to include a bearer token as it is with a raw AJAX request. But, we do have a simple method to append things to the query string.

$.connection.hub.qs = { 'key': value };

So, we’ll use that to append the current bearer token to the hub connection when we have one. With our helper code from above, this becomes as simple as:

Security.acquireAuthToken().done((token) => {
    $.connection.hub.qs = { 'access_token': token };
    $.connection.hub.start();
});

(You may be concerned that the query string isn’t a secure place to store the bearer token but, even using with an API call, the token is still stored unencrypted on the client side and sent with every request so this isn’t much different. Bearer tokens are also transient so they’ll be refreshed periodically.)

We now need to show the Hub how to get the bearer token.

Setting Up The Hub With AD

As with the Web API code above, the set-up for using AD with SignalR comes from the Microsoft.Owin.Security.ActiveDirectory NuGet package.

app.Map("/signalr", map =>
{
    map.UseWindowsAzureActiveDirectoryBearerAuthentication(new WindowsAzureActiveDirectoryBearerAuthenticationOptions
    {
        Provider = new QueryStringOAuthBearerProvider(),
        Tenant = AppSettings["ida:Tenant"],
        TokenValidationParameters = new TokenValidationParameters
        {
            ValidAudience = AppSettings["ida:ClientId"]
        }
    });

    map.RunSignalR(hubConfiguration);
});

This is pretty much the same as for Web API, with the exception of line 5 specifying a custom bearer token provider. It’s this class that will pull the bearer token from the query string value we injected on the client, and insert it into the AD pipeline.

There are a bunch of implementations of this around the web, but the version I ended up with is this:

public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
    public override Task RequestToken(OAuthRequestTokenContext context)
    {
        var value = context.Request.Query.Get("access_token");

        if (!string.IsNullOrEmpty(value))
        {
            context.Token = value;
        }

        return Task.FromResult<object>(null);
    }
}

All this is doing is pulling the token from the request query string and inserting it into the context. The ADAL helpers then process it as normal.

Authorizing the Hub

Now that’s done, we can add an Authorize attribute to our code to authenticate. The ClaimsPrincipal for the authorised user is stored in the Context.User property of the hub.

So, using these together, we get:

[Authorize]
public class UsersHub : Hub
{
    public async override Task OnConnected()
    {
        var user = this.Context.User as ClaimsPrincipal;
        Clients.All.newUserHasConnected(user.Identity.Name);
    }
}

And everything else Hub-related should continue to work as normal.

Kevin Wilson

.NET developer, JavaScript enthusiast, Android user, Pebble wearer, sometime musician and occasional cook.

One thought on “Authorize Your Azure AD Users With SignalR

Leave a Reply

Your email address will not be published. Required fields are marked *