DEV Community

Cover image for Securing Blazor in All Its Flavors
Andrea Chiarelli for Auth0

Posted on

Securing Blazor in All Its Flavors

Since its first official release in .NET 3.0, Blazor has made a solemn promise to .NET developers: allow them to build web applications using just C# and the .NET platform, with no JavaScript and very little HTML. But Blazor’s goal was even more ambitious: becoming the reference framework for all types of applications, not just web.

Since then, the Blazor framework has gained considerable popularity among .NET developers, but not without some problems, inevitably due to its evolution. The framework's changes since its first release have significantly impacted developers' work, who have had to adapt their applications to accommodate its evolution.

Furthermore, while the idea of ​​having a single framework to develop any type of application may seem great, this can hide pitfalls and complications when implementing standards-based authentication and authorization, such as those based on OpenID Connect (OIDC) and OAuth 2.0, for example. As you may know, you should use different flows to authenticate users based on the application type: server-rendered web application, single-page application, or native application.

In this article, we will focus on implementing authentication based on the different types of applications you can build with Blazor. We will provide guidance on best practices and direct you to practical implementations using Auth0.

The Blazor “Flavors”

We mentioned Blazor’s evolution earlier. Let’s briefly recap what it is about.

In the beginning, Blazor was released with two hosting models: Blazor Server and Blazor WebAssembly, also known as Blazor WASM.

Blazor Server applications are intended to run on the server, and the UI part sent to the browser communicates with the server part over a SignalR connection using WebSocket.

Blazor WASM applications are compiled into WebAssembly and run entirely on the browser. While you can host a Blazor WASM application on any web server (standalone Blazor WASM), a specific hosting model called ASP.NET Core hosted model allows you to simply host your application within an ASP.NET Core application, giving you more control over some server-side functionality.

With .NET 6.0, a new hosting model was introduced: Blazor Hybrid. This hosting model allows you to use the Blazor framework to build desktop and mobile applications using a hybrid approach, i.e., Razor components run directly in the native application.

With .NET 8.0, a significant change was introduced: render modes. This revolutionized the way the hosting model was understood until now. With the Server and WASM hosting models, either the entire application ran on the server, or it all ran on the client. With render modes, the components run on the server or client, not the entire application. Or rather, the developer can decide the level of granularity with which to run the various parts of their Blazor application on the server or client. This is a great opportunity to build high-performance web applications, but it also complicates things quite a bit in different contexts, such as authentication and authorization.

At the same time, the introduction of render modes practically deprecates the Blazor Server hosting model. From now on, Blazor applications that support render modes are called Blazor Web Apps.

With the proliferation of all these flavors of Blazor, what is the best approach to implementing OIDC and OAuth 2.0 for authentication and authorization?

Blazor Server

While Blazor Server may be considered deprecated, legacy applications based on this hosting model exist. From the OAuth 2.0 and OpenID Connect perspective, a Blazor Server application is a server-rendered application and should therefore be considered a confidential client. It will use the Authorization Code flow to obtain ID and access tokens.

In code terms, this means that your Blazor application will use the OpenID Connect middleware, as in the following example:

builder.services.AddAuthentication(options =>  
  {  
 options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;  
 options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;  
  })  
 .AddCookie()  
 .AddOpenIdConnect("myOidc", options =>  
  {  
 options.Authority = $"https://{builder.Configuration["Oidc:Domain"]}";

 options.ClientId = builder.Configuration["Oidc:ClientId"];  
 options.ClientSecret = builder.Configuration["Oidc:ClientSecret"];  
 options.ResponseType = "code";  
});
Enter fullscreen mode Exit fullscreen mode

If you use Auth0 for authentication, you can leverage the Auth0 ASP.NET Core Authentication SDK to simplify things.

Read this article to learn how to add Auth0 authentication to your Blazor Server application. As an additional resource, this article explains how to overcome an issue with logout that affects Blazor Server due to SignalR's behavior.

Blazor WebAssembly

Since Blazor WASM applications run entirely in the browser, they can be considered single-page applications, i.e., public clients. In this case, you must use the Authorization Code flow with PKCE to get ID and access tokens.

In terms of implementation, you need a specific WebAssembly package for your application, the Microsoft.AspNetCore.Components.WebAssembly.Authentication package. Your code for configuring OIDC will be as follows:

builder.Services.AddOidcAuthentication(options =>
{
  builder.Configuration.Bind("Oidc", options.ProviderOptions);
  options.ProviderOptions.ResponseType = "code";
});
Enter fullscreen mode Exit fullscreen mode

You also need to add a reference to the AuthenticationService.js script in the index.html file of your application, as shown below:

<!DOCTYPE html>
<html>
  ...
  <body>
    ...
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Read this article for more details on how to add Auth0 authentication to your Blazor WASM application. The article uses the ASP.NET Core hosted model, but the authentication configuration is the same even for standalone Blazor WebAssembly applications. To learn about building Blazor WASM applications to host in Azure Static Web Apps, read this article. Finally, take a look at this article to learn how to use Auth0’s Role-Based Access Control (RBAC) in both Server and WASM hosting models.

The Chaos of Blazor Render Modes

As you can see, identifying the correct authentication approach with the Server and WASM hosting models is quite straightforward: the first follows the confidential client approach, and the second follows the public client approach.

The introduction of render modes complicates things a bit. Indeed, in this case, part of an application runs on the server, and part runs on the client. To complicate the situation even more, with the interactive auto render mode, a component is initially rendered on the server and then on the client. What category does a Blazor application fall into in this case? Is it a confidential client or a public client?

Actually, there is no definitive general answer to this question. Much depends on how the components and their related render modes are combined in a specific application. A conservative approach to implementing OIDC authentication in a Blazor Web App is to consider it a confidential client. This entails that you can reuse the same code to configure a Blazor Server application.

However, this opens a new problem: how do you determine whether or not a user can see a specific component based on its authentication state? If a user authenticates after the browser downloads a WASM component, how does the browser know the new user authentication state?

To solve this problem, you need to keep the server-side and client-side authentication states in sync. However, while you have to manually implement authentication state sync in .NET 8.0, .NET 9.0 simplifies this approach by providing native support for authentication state synchronization.

Read this article to learn the details of how to add support to Blazor Web Apps authentication.

However, problems are not just related to authentication. You should also take care of how to call remote APIs from your WASM components. Since you consider your Blazor Web App a confidential client, your WASM components do not receive any access token to call APIs. How can you deal with this scenario?

Don't be tempted to make the access token available to WASM components. As security best practices suggest, the best approach is to implement the Backend for Frontend (BFF) pattern. This article shows how to call APIs by implementing BFF in Blazor.

Blazor Hybrid Apps

As mentioned earlier, Blazor Hybrid allows you to reuse Razor components and the skills learned with the Blazor framework to create UIs for desktop and mobile applications. In this context, your Blazor application is hosted by a native application, typically developed with MAUI. In fact, the combination of Blazor and MAUI is also often called Blazor MAUI.

In this scenario, authentication is handled entirely by the native component of the Blazor MAUI application. That is, the OIDC configuration and the authentication process occur in the native code of MAUI. The Blazor part simply creates an authentication state based on the user authentication result. Here is an example of an authentication state definition for a Blazor MAUI application using the Auth0 MAUI SDK:

public class Auth0AuthenticationStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());
    private readonly Auth0Client auth0Client;

    public Auth0AuthenticationStateProvider(Auth0Client client) {
        auth0Client = client;
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(new AuthenticationState(currentUser));

    public Task LogInAsync()
    {
        var loginTask = LogInAsyncCore();
        NotifyAuthenticationStateChanged(loginTask);

        return loginTask;

        async Task<AuthenticationState> LogInAsyncCore()
        {
            var user = await LoginWithAuth0Async();
            currentUser = user;

            return new AuthenticationState(currentUser);
        }
    }

    private async Task<ClaimsPrincipal> LoginWithAuth0Async()
    {
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());
        var loginResult = await auth0Client.LoginAsync();

        if (!loginResult.IsError) {
          authenticatedUser = loginResult.User;
        }
        return authenticatedUser;
    }

    public async void LogOut()
    {
        await auth0Client.LogoutAsync();
        currentUser = new ClaimsPrincipal(new ClaimsIdentity());
        NotifyAuthenticationStateChanged(
            Task.FromResult(new AuthenticationState(currentUser)));
    }
}
Enter fullscreen mode Exit fullscreen mode

Read this article for more details about adding Auth0 authentication to a Blazor MAUI application.

Use the Auth0 Templates

As you may have understood by now, there are many moving parts in the process of integrating OIDC and OAuth 2.0 with the many flavors of Blazor. Understanding why and how each approach works is important. It allows you to identify the source of any problems and understand why something goes wrong.

Once you understand which approach to take for each Blazor flavor, you just need to correctly implement the integration for each new application.

Auth0 provides you with project templates to make this job easier. The Auth0 Templates for .NET package includes a few project templates for Blazor that you can use with the .NET CLI, Visual Studio, or JetBrains Rider.

For example, the Auth0 Blazor Web Application template allows you to scaffold a Blazor Web App application with Auth0 authentication embedded with just a command like the following:

dotnet new auth0blazor -o MyBlazorWebApp
Enter fullscreen mode Exit fullscreen mode

If you have the Auth0 CLI installed on your machine and logged in to your Auth0 tenant, you can also get your application automatically registered with Auth0 and ready to run in less than one minute.

Alternatively, you can use your preferred IDE, such as Visual Studio, to generate your application and register it manually:

Auth0 Blazor Web App template

The package also provides a legacy Blazor Server template and a Blazor WebAssembly template in the ASP.NET Core hosted model.

Summary

At this point, you have a comprehensive overview of the different flavors of Blazor available so far and the issues you face when adding support for OIDC and OAuth 2.0-based authentication and authorization.

If you use Auth0 as your identity provider, follow the Blazor resources mentioned here. They will allow you to understand how to integrate authentication and authorization correctly. Furthermore, the Auth0 Templates for .NET package will allow you to have applications ready to use in minutes.

Leave your experience using authentication and authorization with Blazor in the comments.

Top comments (0)