Azure AD authentication in Blazor using ADAL.js

Although Blazor is in early stages of development it is already good enough to try out and play with it. As a logical continuation to my previous experiment where I made Blazor application use Azure Functions based back-end I made it also support Azure AD authentication on web application and back-end level. This blog post introduces my work on Blazor and Azure AD.

Source code available! Source code for this post is available in my GitHub repository gpeipman/BlazorDemo. I expect you to keep the code open while going through this blog post. Where adequate I also have links to specific files in solution provided.

Warning! This solution is not something final or permanent. It is proof-of-concept level solution with known issues on both sides – my and Blazor side. If you know solutions to most common problems here then please feel free to add your ideas to comments section of this post.

Problem and solution

As Blazor is browser-based technology we can think about it as a neighbour of JavaScript. Although Blazor is very young technology in its early phases it already supports JavaScript interoperability. We can call JavaScript functions from Blazor and C# functions from JavaScript. For JavaScript we have millions of libraries available and one of these supports Azure AD: Active Directory Authentication Library (ADAL) for JavaScript.

ADAL library takes automatically care of tokens but it doesn’t come easy as there is method with callback involved. It means that we cannot just have one function in JavaScript that returns valid token. Instead we have to call method, provide it with callback where token is given and then we can get back to Blazor. In Blazor it means I have to delay my requests for data until I get back valid token.

It leads us to some ping-pong between Blazor and JavaScript.

Azure AD ping-pong between Blazor and JavaScript

I managed to make this flow work. There are sometimes JavaScript errors probably because of threading issues. From Blazor issue tracker I found out that Blazor team is aware of this problem and they will target it in future releases.

Preparing and configuring infrastructure

Before we start coding we need all pieces of solution to be set up and configured. The following diagram gives a high level overview about what needs to be done to mimic my solution.

Blazor solution infrastructure

I don’t stop here but give references to materials that help you to set up similar environment.

Using ADAL JavaScript library

ADAL JavaScript library is one JavaScript file. We include it in index.html file (under wwwroot folder). This library provides us with everything needed to communicate with Azure AD.

<script type="blazor-boot">
</script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<script src="//static.gunnarpeipman.com/js/adal.js"></script>
<script src="//static.gunnarpeipman.com/js/app.js"></script>

app.js is a file where I have all Azure AD and UI related JavaScript code. We configure ADAL authentication context first and then handle the situation when browser was redirected back from Azure AD login page. Finally we check if user is authenticated and if not then we send him or her to Azure AD login page.

var authContext = null;
var user = null;

(function () {
    window.config = {
        instance: 'https://login.microsoftonline.com/',
        tenant: '<Your tenant URL>',
        clientId: <Your application ID>',
        postLogoutRedirectUri: window.location.origin,
        cacheLocation: 'localStorage' // enable this for IE, as sessionStorage does not work for localhost.
    };

    authContext = new AuthenticationContext(config);
    var isCallback = authContext.isCallback(window.location.hash);
    authContext.handleWindowCallback();
    //$errorMessage.html(authContext.getLoginError());

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

    user = authContext.getCachedUser();
    if (!user) {
        authContext.login();
    }

}());

// Skipped some UI related functions

Blazor.registerFunction('executeWithToken', (action) => {
    authContext.acquireToken(authContext.config.clientId, function (error, token) {
        let tokenString = Blazor.platform.toDotNetString(token);

        const assemblyName = 'BlazorDemo.AdalClient';
        const namespace = 'BlazorDemo.AdalClient';
        const typeName = 'AdalHelper';
        const methodName = 'RunAction';

        const runActionMethod = Blazor.platform.findMethod(
            assemblyName,
            namespace,
            typeName,
            methodName
        );

        Blazor.platform.callMethod(runActionMethod, null, [
            action, tokenString
        ]);

    });

    return true;
});

executeWithToken() is function known to Blazor. Here you can see why we need a magic described above. authContext.acquireToken() method may make call to Azure AD. On JavaScript calls to other servers are asynchronous but we can usually define callback function that is called when function is done its work. In callback function we have get valid Azure AD token and we send it to Blazor with action we sent from Blazor to executeWithToken() method.

Okay, it gets confusing, I know, but it gets clear for the end of this blog post.

Delaying data binding in Blazor

Before focusing to how to run the action we need one from Blazor. In demo application I have Index.cshtml page where books table is filled. If we don’t have JavaScript callbacks on our way then we can go with simple version of LoadBooks() method like shown here.

private async Task LoadBooks(int page)
{
    Books = await BooksClient.ListBooks(page);
}

When ADAL is involved it doesn’t go so easily. We need bearer token to call Azure Functions based back-end that is protected by Azure AD. Here is the ADAL JavaScript version of same Blazor method (code-behind file of Index.cshtml in my demo project).

private void LoadBooks(int page)
{
    Action<string> action = async (token) =>
    {
        BooksClient.Token = token;
        Books = await BooksClient.ListBooks(page);

        StateHasChanged();          
    };

    RegisteredFunction.InvokeUnmarshalled<bool>("executeWithToken", action);
}

As we don’t have bearer token available when LoadBooks() method is called we create the action where token is given in. Then we use the token with BooksClient instance and ask list of books from back-end Azure Function. Finally we notifiy Blazor that UI must be rendered again as something changed. Notice that this action is not run immediately. It is on-hold until it is actually called somewhere in C# code.

InvokeUnmarshalled() method calls JavaScript function executeWithToken() and provides action as an argument. We must use unmarshalled version of Invoke() method here because it is giving arguments in by reference. Invoke() method would end up with error because it will serialize all arguments to JSON and if it is not possible then exception is thrown.

Invoking Blazor action in token refreshing callback

When ADAL has successfully found bearer token for us our JavaScript function executeWithToken() runs its callback that invokes RunAction() method in Blazor.

Blazor.registerFunction('executeWithToken', (action) => {
    authContext.acquireToken(authContext.config.clientId, function (error, token) {

        let tokenString = Blazor.platform.toDotNetString(token);

        const assemblyName = 'BlazorDemo.AdalClient';
        const namespace = 'BlazorDemo.AdalClient';
        const typeName = 'AdalHelper';
        const methodName = 'RunAction';

        const runActionMethod = Blazor.platform.findMethod(
            assemblyName,
            namespace,
            typeName,
            methodName
        );

        Blazor.platform.callMethod(runActionMethod, null, [
            action, tokenString
        ]);

    });

    return true;
});

Right now we can only call static methods of Blazor classes but it is okay for us. So, here is my action runner implemented as a class on Blazor. All it does is it runs the action provided and gives token in.

public static class AdalHelper
{
    public static async Task RunAction(Action<string> action, string token)
    {
        await Task.Run(() => action(token));
    }
}

For some reason using Task was the only way how I got this action to run.

Blazor books list

Wrapping up

Although it is not yet supported scenario we were able to make Blazor support Azure AD authentication for web application and Azure Functions based back-end service. This is proof-of-concept style solution where not all scenarios for JavaScript interop are supported yet. But it still good showcase about how to use newest Microsoft technologies to build modern eneterprise applications.

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    4 thoughts on “Azure AD authentication in Blazor using ADAL.js

    Leave a Reply

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