In this article, we are going to learn how to use Azure Active Directory (Azure AD) to secure our Blazor WebAssembly standalone application. First, we are going to show you how to use the Azure portal to register the Blazor Webassembly standalone application. Then, we are going to use the required information to connect our app to that Azure Active Directory. Finally, we are going to see what we get out of the box from the creation templates. We can use that information to add the Azure AD auth to an existing Blazor WASM application.

To download the source code for this article, you can visit our Blazor WebAssembly Standalone with Azure AD repository.

If you want to learn more about Blazor WebAssembly, we strongly suggest visiting our Blazor WebAssembly series of articles, where you can read about Blazor WebAssembly development, authentication, authorization, JSInterop, and other topics as well.

So, let’s start.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

App Registration with Azure Active Directory

The first thing we have to do is to navigate to Azure Active Directory in our Azure Portal:

Azure Active Directory source

Then, under the Manage sections, we are going to choose App registrations, and click the New registration link:

App registration in Azure Active Directory 

In a new window, we have to provide the required information:

App details for registration

We can see, we have to provide a name for the app, the account we are going to use, state that this is a single page application, and provide a redirect URI. We can safely provide this URI because we will force our app to run on port 5001. After populating all the required fields, we can click the Register button.

Once we click that button, Azure will register our app and navigate us to the Overview page, where we can see all the details about our app:

App client ID and Tenant ID values

Soon, we are going to need the Application (client) ID and Directory (tenant) ID values so, we can save them in a document or just navigate back to the Overview page to copy these values.

Final Confirmation

To confirm our registration, we can navigate to the Authentication page under the Manage section:

Azure AD information confirmation

Here, we can confirm our redirect URI is valid and also we can see that we are going to use Authorization Code Flow with PKCE, which is a recommended flow for a SPA. If you want to use Implicit or Hybrid flow, you can check any of the two checkboxes (Access tokens and ID tokens) provided a bit below on the Authentication page. Since we are using the Authorization Code flow, we are not going to select any of these options.

Creating an Application

We can create our application in two different ways. Via dotnet command or by using the Visual Studio template creation.

If we want to use the command, we have to create a new empty folder, open Command Prompt, navigate to that folder and execute the command:

dotnet new blazorwasm -au SingleOrg --client-id "{CLIENT ID}" -o {APP NAME} --tenant-id "{TENANT ID}"

We can see the placeholders for the client id, application name, and tenant id values. We already know the Application (client) ID and the Directory (tenant) ID values – from the Overview page. So, we can copy them and add them to the command. Also, we have to add our application’s name:

dotnet new blazorwasm -au SingleOrg --client-id "3b3ce0ca-3558-45c3-8096-d506d1515b47" -o BlazorWasmStandaloneAAD --tenant-id "acd9a136-2bfa-485e-934b-4fb1c6fea421"

If you use this way of creating the application, it is going to prepare everything for you and add provided information on the required places in the app.

We are going to use Visual Studio (the second way) so we could show you step by step what is going on behind the scene.

That said, let’s create a new Blazor WebAssembly project, add it a name: BlazorWasmStandaloneAAD, and choose Microsoft Identity Platform for the authentication type:

Microsoft Identity Platform Authentication Type

Once we create the application, we can open the appsettings.json file (under the wwwroot folder) and modify the template data:

"AzureAd": {
    "Authority": "https://login.microsoftonline.com/acd9a136-2bfa-485e-934b-4fb1c6fea421",
    "ClientId": "3b3ce0ca-3558-45c3-8096-d506d1515b47",
    "ValidateAuthority": true
}

In the Authority part, we combine the Microsoft online URI (https://login.microsoftonline.com/) and our tenant ID value. Also, we populate the ClientId property with the value of our Application ID.

Finally, to force our app to start using port 5001, we are going to modify the launchSettings.json file:

{
    "profiles": {
        "BlazorWasmStandaloneAAD": {
            "commandName": "Project",
            "dotnetRunMessages": "true",
            "launchBrowser": true,
            "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
            "applicationUrl": "https://localhost:5001;http://localhost:5000",
            "environmentVariables": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            }
        }
    }
}

That’s all we need in that file.

Testing

Now, we can start our app.

As soon as it starts, we will be able to see the Login link next to the About link.

After we click that link, we are going to see the popup window requiring our credentials and another one for consent.

Once we click the SignIn button, the app redirects us to the Home page, and we can see the welcome message with our username:

Successful Azure Active Directory Authentication with Blazor WASM Standalone app

Also, we can see the Log out link, which we can use to log out from the app.

Now, let’s inspect all the application components that make this possible.

Application Overview

We have already seen the appsetings file, where we populate the required information (App ID and Tenant ID). But, let’s see how we make use of that info in our app.

The first important thing is the Msal authentication package:

Msal Authentication Package

Additionally, to be able to read the data from the appsettings file, we are using the AddMsalAuthentication method in the Program.cs class:

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});

This method accepts a callback where we can configure various settings for the authentication. Right now, we only fetch the data from the appsettings file. But, we can provide a default  access token scopes, or if we want, we can set the login method to redirect instead of using a popup window:

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
    options.ProviderOptions.DefaultAccessTokenScopes.Add("https://graph.microsoft.com/User.Read");
    options.ProviderOptions.LoginMode = "redirect";
});

You can find this scope in the API permissions menu under the Manage section of our registered application:

User.Read Graph permission

By adding a second option, as soon as we click the login button, the app redirects us to the login form instead of showing the popup window.

Additional Components and Files

If we inspect the index.html file, we are going to see the reference to this package’s js file:

<!DOCTYPE html>
<html>

<head>
    ...
</head>

<body>
    <div id="app">Loading...</div>

    ...
    <script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script>
    <script src="_framework/blazor.webassembly.js"></script>
</body>

</html>

This script is important for handling authentication actions that we perform in our app.

It is also important to notice the @using Microsoft.AspNetCore.Components.Authorization using directive inside the Imports.razor file. This way it is enabled throughout the entire application.

Next, we can open the App.razor file:

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    @if (!context.User.Identity.IsAuthenticated)
                    {
                        <RedirectToLogin />
                    }
                    else
                    {
                        <p>You are not authorized to access this resource.</p>
                    }
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

We can see different components here like CascadingAuthenticationState, AuthorizeRouteView, NotAuthorized. To learn more about how these components work and why do we wrap our code inside the CascadingAuthenticationState component, you can read our AuthenticationStateProvider article. Basically, with the CascadingAuthenticationState component, we expose the authentication state to the rest of the app. Additionally, we use the AuthorizeRouteView component to determine whether the user is authorized or not. If they are not, we use the RedirectToLogin component to redirect the user to the login page.

RedirectToLogin and Authentication Components

If we inspect the RedirectToLogin component:

@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
    protected override void OnInitialized()
    {
        Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
    }
}

We can see it uses the NavigationManager service to navigate the user to the Authentication component with the login action as a parameter and additional query string for returnUrl.

So, if we open the Pages folder, we are going to find this component inside:

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter] public string Action { get; set; }
}

It only calls the RemoteAuthenticatorView component, which includes a default set of UI pieces for each authentication state. If we want, we can modify all these default UI components.

Log in and Log out Links

If we open the LoginDisplay.razor file, we are going to see the code responsible for showing the Log in and Log out links:

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity.Name!
        <button class="nav-link btn btn-link" @onclick="BeginLogout">Log out</button>
    </Authorized>
    <NotAuthorized>
        <a href="authentication/login">Log in</a>
    </NotAuthorized>
</AuthorizeView>

@code{
    private async Task BeginLogout(MouseEventArgs args)
    {
        await SignOutManager.SetSignOutState();
        Navigation.NavigateTo("authentication/logout");
    }
}

If the user is authenticated, we are going to see the “Hello” message with the user’s username, and the Log out button. Otherwise, we are going to see the Log in button.

Protecting the Page

Currently, all of our components are accessible to unauthorized users. So, let’s change that.

Let’s open the FetchData file and add the [Authorize] attribute:

@page "/fetchdata"
@inject HttpClient Http
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]

<h1>Weather forecast</h1>

And that’s all it takes. You can read more about different ways of protecting your components in the previously linked article.

Now, if we start the app, and try to visit the FetchData page, the app will navigate us to the Login page first.

Conclusion

We can see how easy it is to use Azure Active Directory to secure our Blazor application. With a few registration steps on the Azure portal and a few changes in the code, we can create a secured application in a matter of minutes. 

So, that’s it for this one and until the next article,

All the best. 

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!