DEV Community

Cover image for Securing a Blazor WebAssembly Hosted Apps with Azure Active Directory - Part 2
Emanuele Bartolesi
Emanuele Bartolesi

Posted on

Securing a Blazor WebAssembly Hosted Apps with Azure Active Directory - Part 2

In the previous article of this series, we have created a secure project with Blazor WebAssembly Hosted and Azure Active Directory.
In this article we are going to add another level of security: the roles.

Also in this case we will use a feature of Azure Active Directory called: "App Roles".

So, let's open our application and make some changes to it!

Add App Roles to the App Registration

From the Azure Portal, come back to the App Registrations and search for your two apps.

Image description

Open the app registration for the server app and under the "Manage" section of the left navigation pane, click on "App roles".

Image description

Click on the button "Create app role" on the top bar navigation and fill the form with the information about the new role.

Image description

Pay attention to the field "value" because it will be the value that we will use in our code later.
It's reccomended to use lowcase letters.

Repeat these operations for other roles as: Viewer, PowerUser, etc.

Copy the App roles to the client App Registration

We have to do the same steps for the client app.
But it's very important to use the same values for this application.
The faster way is to copy the information about App roles directly from the Manifest file and put them in the manifest of the client application.

From the left navigation pane, click on "Manifest", select the Json portion of the code about appRoles and copy it in the clipboard.

Image description

Now you can open the manifest file of the client application and replase the Json node with the new values.

That's it.

Assign users to the new roles

Navigate to the "Enterprise Applications and open the server app.

Image description

Under the menu "Manage" click on "Users and groups" and then "Add user/group".

Image description

In the new window, search and select a user and the role that you want to assign to him.

Image description

Now in the list you can see all the users with their role assigned.

Image description

Important: repeat all these steps for the client application.

Use the App roles in the application

Open the Client project and add a class called "SecureUserAccount" (but you can choose the name that you prefer).
This class extends the RemoteUserAccount.

    public class SecureUserAccount : RemoteUserAccount
    {
        [JsonPropertyName("roles")]
        public string[] Roles { get; set; } = Array.Empty<string>();
    }
Enter fullscreen mode Exit fullscreen mode

Now our application need to know how to manage the roles from Azure AD.
To do that, we can create a new class in the client project called "SecureAccountFactory"

    public class SecureAccountFactory : AccountClaimsPrincipalFactory<SecureUserAccount>
    {
        public SecureAccountFactory(IAccessTokenProviderAccessor accessor)
            : base(accessor)
        {
        }
        public async override ValueTask<ClaimsPrincipal> CreateUserAsync(SecureUserAccount account,
            RemoteAuthenticationUserOptions options)
        {
            var initialUser = await base.CreateUserAsync(account, options);
            if (initialUser.Identity.IsAuthenticated)
            {
                var userIdentity = (ClaimsIdentity)initialUser.Identity;
                foreach (var role in account.Roles)
                {
                    userIdentity.AddClaim(new Claim("appRole", role));
                }
            }
            return initialUser;
        }
    }
Enter fullscreen mode Exit fullscreen mode

The last change in the client application is in the Program.cs file.
Replace the Msal authentication snippet with the new one:

builder.Services.AddMsalAuthentication<RemoteAuthenticationState, SecureUserAccount>(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
    options.ProviderOptions.DefaultAccessTokenScopes.Add("api://957e1304-dd53-4589-b013-4a264dd19334/BlazorWASMHosted.API");
    options.UserOptions.RoleClaim = "appRole";
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, SecureUserAccount,
    SecureAccountFactory>();
Enter fullscreen mode Exit fullscreen mode

Now it's time to change the server application.

Open the Program.cs file and replace the MsalAuthentication code with the new one:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(options =>
        {
            builder.Configuration.Bind("AzureAd", options);
            options.TokenValidationParameters.RoleClaimType =
                "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
        },
        options => { builder.Configuration.Bind("AzureAd", options); });
Enter fullscreen mode Exit fullscreen mode

Securing Pages, Web API and Features

Let's start to work with the roles.

First of all, the server application.
Extend the Authorize attribute of the WeatherForecastController with the roles property:

[Authorize(Roles = "administrator")] 
Enter fullscreen mode Exit fullscreen mode

Now only a user with the Administrator role can call the controller.

In the client app, open the Fetchdata.razor file and extend the same attribute with the roles attributes:

@attribute [Authorize(Roles = "administrator")]
Enter fullscreen mode Exit fullscreen mode

Display only the links for your role

If you want to prevent that a user access to a page with the wrong role, you can hide portions of the pages or links in the application.
You can use one of the out-of-box features of Blazor called "AuthorizeView".

For instance, if you want to hide the page Fetch data, it's enough if you add this code in the NavMenu.razor file.

<AuthorizeView Roles="administrator">
    <Authorized>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
    </Authorized>
</AuthorizeView>
Enter fullscreen mode Exit fullscreen mode

Access to the App Role from the code behind

It's very easy also to access to the roles of the user from the code behind.
Very often inside an application there a lot of code that change the behaviour based on the user permissions.

Let's start with an easy example.
Open the Counter.razor page and change the code as below:

@code {
    [CascadingParameter]
    public Task<AuthenticationState> AuthState { get; set; }

    private int currentCount = 0;

    private async Task IncrementCount()
    {
        var authState = await AuthState;
        var user = authState.User;

        if (user.IsInRole("viewer"))
        {
            currentCount += 2;
        }

        if (user.IsInRole("administrator"))
        {
            currentCount -= 2;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Wrap up

Now you know how to create a new app on Azure Portal, assign roles to it and work with the roles in your Blazor applications.

You can find the source code of the example on GitHub.
Follow the instructions in the Readme file.

image

BlazorWASMHostedAzureADAuth

This repository is an example for Blazor WASM Hosted app secured by Azure Active Directory.

There are two different branches.

  • Authorization
  • Roles

You can find the right code based on the two post on my blog with all the steps that you need to replicate these settings in your own projects.

The link to the series: https://dev.to/kasuken/series/17352

Thank you and drop me a comment if you have questions or feedbacks!

Top comments (4)

Collapse
 
vipehowlett profile image
Evan Howlett • Edited

I followed your tutorial, and after a month of trying to get this to work on my own, I was finally able to make progress. However I still haven't fully gotten it to work. I am fully authorized in the front end with all the roles and what not available, and I can hit api routes that just have the [Authorize] attribute, however when I want to specify the role, nothing I do works. I've spent two days trying to figure out why.

I turned on logging for the JWT on the backend and it validates everything properly. Even when I decode the token it has "roles: {list of roles}". However, when I use [Authorize(Roles = "{role in list}")] I get nothing but 403 errors. Any advice?

Collapse
 
vipehowlett profile image
Evan Howlett

Instead of doing

AddMicrosoftIdentityWebApi(options =>
        {
            builder.Configuration.Bind("AzureAd", options);
            options.TokenValidationParameters.RoleClaimType =
                "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
        },
        options => { builder.Configuration.Bind("AzureAd", options); });
Enter fullscreen mode Exit fullscreen mode

do

AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
Enter fullscreen mode Exit fullscreen mode
Collapse
 
timcadieuxgarren profile image
tim-cadieux-garren

Hello, thank you for the above series, I was able to get the Client side working with roles, I have tested this using the Counter user.IsInRole. I am however unable to get the FetchData controller to work. Sometimes I can get a 403 (Forbidden) error to appear but usually its just the standard Blazor '<' is an invalid start of a value error.

Would you have any advice as to where to look. I'm new to both AAd and using AppRoles.

Thank you in advance.

Collapse
 
vipehowlett profile image
Evan Howlett

This happens when you the controller and the razor page have the same route. Assign a route to the controller function you want to execute and call it that way.