Back to all posts

.NET MAUI Blazor Hybrid - Custom Authentication Implementation

Posted on Apr 01, 2022

Posted in category:
Development
.NET

Recently I decided to take a deep dive into the world of .NET MAUI Blazor Hybrid to see how it compared to the traditional Xamarin.Forms development that I am so used to. The process for the initial setup was fantastic, and I found the power that was at my fingertips to be quite impressive. However, I was amazed that I couldn't find a solid solution for authentication that would meet my needs, so I thought I'd share it here.

Disclaimer This blog post was written about preview release software, specifically MAUI Preview 14, from March of 2022, it is possible that this information will change or that the implementation/process below will not be viable for production releases once MAUI is fully released.

My Goal

The overall architecture of the mobile application I was testing is quite simple. The existing Mobile application presents a login form and calls an API on a .NET Core backend. The API returns a JWT for future API calls, the mobile application stores this in Secure Storage. I wanted to preserve this functionality in my Blazor Hybrid application; however, I also wanted to have the full support of using Authorization in my Razor Components to control what the user sees; otherwise, I would be writing a bunch of my own processes.

Sounds simple, right? Well, yes, but trying to figure out "how" was the complicated part.

My Solution

I was able to get this working with minimal effort. However, I had to piece together information from multiple sources, so I'll document my steps here.

Additional NuGet Packages

I needed to add the following NuGet packages to my project before completing the remaining steps.

  • Microsoft.AspNetCore.Authorization
  • Microsoft.AspnetCore.Components.Authorization
  • Microsoft.AspNetCore.Components.WebAssembly.Authentication

Created a RedirectToLogin.razor Component

This Razor Component was added to manage the redirection of an unauthenticated user to the login page and was added to the project's `/shared` folder.

RedirectToLogin.razor
@inject NavigationManager navigationManager
        <div class="loader loader-bouncing"><span>Redirecting...</span></div>
        @code{
        
            protected override void OnInitialized()
            {
                navigationManager.NavigateTo("/login");
            }
        }
    

This simply upon initilization redirects to the /login page.

Updated Main.razor to Enable Authentication

The default template for .NET MAUI Blazor does not include the needed elements for the router to support authorization, so I replaced my main.razor content with the below.

Updated Main.razor
@using Microsoft.AspNetCore.Components.Authorization
        <Router AppAssembly="@GetType().Assembly">
            <Found Context="routeData">
                <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                    <Authorizing>
                        Authorizing...
                    </Authorizing>
                    <NotAuthorized>
                        <RedirectToLogin />
                    </NotAuthorized>
                </AuthorizeRouteView>
            </Found>
            <NotFound>
                <CascadingAuthenticationState>
                    <LayoutView Layout="@typeof(MainLayout)">
                        <p>Sorry, there's nothing at this address.</p>
                    </LayoutView>
                </CascadingAuthenticationState>
            </NotFound>
        </Router>
    

The purpose of this code is to set up default support for AuthorizedRoutes, and when a user isn't authorized, we direct them to our login page.

Set Pages to Require Authorization

The next step was to add the [Authorize] Attribute to my pages that required authentication which was as simple as adding the attribute, similar to the following.

Updates to Index.razor
@page "/"
        @using Microsoft.AspNetCore.Authorization
        @attribute [Authorize]
    

Creation of a Custom AuthenticationStateProvider

The real magic of the implementation was the creation of a Custom AuthenticationStateProvider, which allows me to create and share an identity for the user to show that they are authenticated. I only need to differentiate between authenticated and unauthenticated, but you can support much more. My custom implementation looked like the following:

CustomAuthenticationStateProvider Implementation
using Microsoft.AspNetCore.Components.Authorization;
        using System.Security.Claims;
        
        
        namespace FlightFiles.Mobile.Maui
        {
            public class CustomAuthenticationStateProvider : AuthenticationStateProvider
            {
                public IdentityAuthenticationStateProvider()
                {
                }
        
                public async Task Login(string token)
                {
                    await Microsoft.Maui.Essentials.SecureStorage.SetAsync("accounttoken", token);
                    NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
                }
        
                public async Task Logout()
                {
                    SecureStorage.Remove("accounttoken");
                    NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
                }
        
                public override async Task<AuthenticationState> GetAuthenticationStateAsync()
                {
                    var identity = new ClaimsIdentity();
                    try
                    {
                        var userInfo = await SecureStorage.GetAsync("accounttoken");
                        if (userInfo != null)
                        {
                            var claims = new[] { new Claim(ClaimTypes.Name, "ffUser") };
                            identity = new ClaimsIdentity(claims, "Server authentication");
                        }
                    }
                    catch (HttpRequestException ex)
                    {
                        Console.WriteLine("Request failed:" + ex.ToString());
                    }
        
                    return new AuthenticationState(new ClaimsPrincipal(identity));
                }
            }
        }
    

Usage of this is quite simple where I simply call once a user is authenticated and it stores the JWT in secure storage, which sets the user as authenticated. How you code/implement your /login page is up to you, the key here is that once authenticated, you need to set the value here and then you are set to go. Logout is as simple as removing the items from secure storage.

This is of course, an initial attempted implementation, future implementations can be as complex as needed.

Wire Up Services

The final step, before you can run your application and see the request get redirected to your login component is to add the needed items to MauiProgram.cs, similar to the following.

MauiProgram.cs Additions
builder.Services.AddAuthorizationCore();
        builder.Services.AddScoped<CustomAuthenticationStateProvider>();
        builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomAuthenticationStateProvider>());
    

This simply registers authentication, our custom provider, and sets the expected implementation of AuthenticationStateProvider to be our custom one.

Next Steps

If you complete the following steps and implement a login page of your own you now have a simple method to authenticate via an external service and use that within your Blazor Hybrid application. There is still plenty of work to make sure that this is truly a viable method, but understanding the building blocks necessary, I hope, is helpful. Share any feedback below.

You can view a Sample Implementation of this solution as well.