Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Azure AD Authentication in Blazor Using ADAL.js

DZone's Guide to

Azure AD Authentication in Blazor Using ADAL.js

Want to install Azure AD authentication on your enterprise app? Check out this post on how to use ADAL.js in Blazor.

· Security Zone ·
Free Resource

Protect your applications against today's increasingly sophisticated threat landscape.

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

Problem and Solution

Since Blazor is browser-based technology, we can think about it as a neighbor of JavaScript. Although Blazor is a very young technology, 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 automatically takes care of tokens, but it doesn’t come easy. There is a method with callback involved. This means that we cannot just have one function in JavaScript that returns a valid token. Instead, we have to call a method, provide it with callback where the 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 a valid token.

This 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. This is probably because of threading issues. From the Blazor issue tracker, I found out that the 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 the pieces of the solution to be set up and configured. The following diagram gives a high-level overview of what needs to be done to mimic my solution.

Blazor solution infrastructure

I won't stop here. I will provide additional references to materials that help you to set up a similar environment.

Using ADAL JavaScript library

The ADAL JavaScript library is one JavaScript file. We include it in the index.html file (under the 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="/js/adal.js"></script>
<script src="/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;
});


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

It get's confusing — I know. But, it gets clearer by the end of this blog post.

Delaying Data Binding in Blazor

Before focusing on how to run the action, we need one from Blazor. In the demo application, I have an Index.cshtml page where the books table is filled. If we don’t have JavaScript callbacks on our way, then, we can go with a 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 a token to call Azure Functions based in the back-end that is protected by Azure AD. Here is the ADAL JavaScript version of the 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 a bearer token available when the LoadBooks() method is called, we create the action where a token is given. Then, we use the token with the BooksClient instance and ask for the list of books from backend Azure Function. Finally, we notify Blazor that the 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.

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

Invoking the Blazor Action in a Token 

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 that I could get this action to run.

Blazor books list

Wrapping Up

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

Source code for this post is available in my GitHub repository gpeipman/BlazorDemo.

Rapidly detect security vulnerabilities in your web, mobile and desktop applications with IBM Application Security on Cloud. Register Now

Topics:
security ,blazor ,azuread ,authentication ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}