Discover and enable the integrations you need to solve identityAuth0 Marketplace
.NET

Securing Blazor WebAssembly Apps

Learn how to secure Blazor WebAssembly applications with Auth0.

Last Updated On: October 07, 2021

.NET

Securing Blazor WebAssembly Apps

Learn how to secure Blazor WebAssembly applications with Auth0.

Last Updated On: October 07, 2021

Blazor allows you to build your WebAssembly (also known as WASM) applications by leveraging the .NET platform. Thanks to Auth0, you can also easily secure them by adding support for authentication and authorization, as this article will show. Let's start!

Building a Blazor WebAssembly Application

In a previous article, you built a Blazor application by using the Server Hosting model. It was a simple quiz application that shows a list of questions with multiple answers and assigns you a score based on the correct answers you provide.

Now you are going to implement the same application by using the WebAssembly hosting model. To learn more about Blazor hosting models, please check out the specific section in that mentioned article.

As explained in the last article, this hosting model makes your application be compiled in WebAssembly and run in your browser. However, depending on the structure of your project, you have two options to create your application:

  • You may have just the client-side application that will call an existing Web API
  • You may have both the client-side application and the Web API application. In this case, the Web API application also serves the Blazor WebAssembly app to the browsers. This option is called ASP.NET Core hosted.

For this project, you will choose the second option. In fact, you will have the client-side application, that will be responsible for showing the UI and managing the user interaction, and the Web API application, that will provide the quiz to the client.

To build this application, you need .NET 5.0 SDK or above installed on your machine.

You create a new Blazor WebAssembly project by typing the following command:

dotnet new blazorwasm -o QuizManagerClientHosted --hosted

Note: If you want to create only the client-side application, you have to omit the --hosted flag in the previous command.

If you take a look at the QuizManagerClientHosted folder, you will find the folder structure shown below:

QuizManagerClientHosted
│   .gitignore
│   QuizManagerClientHosted.sln    
├── Client
├── Server
└── Shared

Each of these folders contains a .NET project. While the Client and the Server folders are straightforward, you may wonder what the Shared folder contains. It contains a class library project with the code shared by the client-side and the server-side applications. In the case of the application you are going to re-implement, it will contain the data model.

So, move into the Shared folder and remove the WeatherForecasts.cs file. Create a new file in the Shared folder called QuizItem.cs with the following content:

// Shared/QuizItem.cs

using System.Collections.Generic;

namespace QuizManagerClientHosted.Shared
{
    public class QuizItem
    {
        public string Question { get; set; }
        public List<string> Choices { get; set; }
        public int AnswerIndex { get; set; }
        public int Score { get; set; }

        public QuizItem()
        {
            Choices = new List<string>();
        }
    }
}

This class implements the model for each item of the quiz. It provides a question, a list of possible answers, the zero-based index of the correct answer, and the score assigned when the user gives the correct answer.

Creating the Server

Now, move in the Server/Controllers folder and remove the WeatherForecastController.cs file. Then, add in the same folder a new file named QuizController.cs and put the following code inside it:

// Server/Controllers/QuizController.cs

using QuizManagerClientHosted.Shared;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace QuizManagerClientHosted.Server.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class QuizController : ControllerBase
    {
        private static readonly List<QuizItem> Quiz = new List<QuizItem> {
            new QuizItem
                {
                    Question = "Which of the following is the name of a Leonardo da Vinci's masterpiece?",
                    Choices = new List<string> {"Sunflowers", "Mona Lisa", "The Kiss"},
                    AnswerIndex = 1,
                    Score = 3
                },
                new QuizItem
                {
                    Question = "Which of the following novels was written by Miguel de Cervantes?",
                    Choices = new List<string> {"The Ingenious Gentleman Don Quixote of La Mancia", "The Life of Gargantua and of Pantagruel", "One Hundred Years of Solitude"},
                    AnswerIndex = 0,
                    Score = 5
                }
            };

        [HttpGet]
        public List<QuizItem> Get()
        {
            return Quiz;
        }
    }
}

As you can see, this is the Web API version of the QuizService class you created in the Blazor server application. You notice the initialization of the Quiz static variable with a few QuizItem instances and the definition of the Get() action returning that variable.

For more information on how to create a Web API in .NET Core, see this tutorial.

Creating the Client

In order to create the Blazor client application, move into the Client/Pages folder and remove the Counter.razor and the FetchData.razor files. Then, add to this folder a file named QuizViewer.razor with the following content:

// Client/Pages/QuizViewer.cs

@page "/quizViewer"
@using QuizManagerClientHosted.Shared
@inject HttpClient Http

 <h1>Take your quiz!</h1>
 <p>Your current score is @currentScore</p>

@if (quiz == null)
{
    <p><em>Loading...</em></p>
}
else
{
    int quizIndex = 0;
    @foreach (var quizItem in quiz)
    {
        <section>
            <h3>@quizItem.Question</h3>
            <div class="form-check">
            @{
                int choiceIndex = 0;
                quizScores.Add(0);
            }
            @foreach (var choice in quizItem.Choices)
            {
                int currentQuizIndex = quizIndex;
                <input class="form-check-input" type="radio" name="@quizIndex" value="@choiceIndex" @onchange="@((eventArgs) => UpdateScore(Convert.ToInt32(eventArgs.Value), currentQuizIndex))"/>@choice<br>

                choiceIndex++;
            }
            </div>
        </section>

        quizIndex++;
    }
}

@code {
    List<QuizItem> quiz;
    List<int> quizScores = new List<int>();
    int currentScore = 0;

    protected override async Task OnInitializedAsync()
    {
        quiz = await Http.GetFromJsonAsync<List<QuizItem>>("Quiz");
    }

    void UpdateScore(int chosenAnswerIndex, int quizIndex)
    {
        var quizItem = quiz[quizIndex];

        if (chosenAnswerIndex == quizItem.AnswerIndex)
        {
            quizScores[quizIndex] = quizItem.Score;
        } else
        {
            quizScores[quizIndex] = 0;
        }
        currentScore = quizScores.Sum();
    }
}

The @page directive defines this Razor component as a page, which is a UI element that is directly reachable through an address (/quizViewer in this case) in the Blazor's routing system. Then, you have the @using directive, which provides access to the shared data model created above (QuizItem.cs). The @inject directive asks the dependency injection system to get an instance of the HttpClient class.

After these initializations, you find the markup defining the UI. As you can see, this part is a mix of HTML and C# code whose purpose is to build the list of questions with the respective possible answers represented as radio buttons.

The final block of the component is enclosed in the @code directive. This is where you put the logic of the component. In the case of the QuizViewer component, you have the OnInitializedAsync() and the UpdateScore() methods. The first method is called when the component is initialized, and it basically gets the quiz data by invoking the Quiz endpoint of the Web API you created before. The UpdateScore() method is called when the user clicks one of the proposed answers and it updates the list of the assigned scores according to the answer chosen by the user. In the same method, the value of the current score is computed and assigned to the currentScore variable. The value of this variable is shown above the list of questions, as you can see in the markup.

To complete your application, replace the content of the NavMenu.razor file in the Client/Shared folder with the following code:

// Shared/NavMenu.razor

 <div class="top-row pl-4 navbar navbar-dark">
   <a class="navbar-brand" href="">QuizManager</a>
   <button class="navbar-toggler" @onclick="ToggleNavMenu">
       <span class="navbar-toggler-icon"></span>
   </button>
 </div>

 <div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
   <ul class="nav flex-column">
       <li class="nav-item px-3">
           <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
               <span class="oi oi-home" aria-hidden="true"></span> Home
           </NavLink>
       </li>
       <li class="nav-item px-3">
           <NavLink class="nav-link" href="quizViewer">
              <span class="oi oi-list-rich" aria-hidden="true"></span> Quiz
           </NavLink>
       </li>
   </ul>
 </div>

@code {
   bool collapseNavMenu = true;
   string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

   void ToggleNavMenu()
   {
       collapseNavMenu = !collapseNavMenu;
   }
}

As you may have noticed, this code redefines the navigation menu by removing the default items pointing to the Counter and FetchData components and including an item to reach the QuizViewer component.

Running your Blazor WebAssembly Application

Your Blazor WebAssembly application is complete. In the root folder of the project, type the following command:

dotnet run --project Server

Pointing your browser to the https://localhost:5001 address, you should see the following page:

Blazor App Home page

Clicking the Quiz item on the navigation bar, you should be able to take a simple quiz as shown in the following screenshot:

Blazor Quiz Page

Even if the look and feel of this application is basically the same as the Blazor Server implementation, the application architecture is quite different. In this case, you have the client side compiled into WebAssembly and running in your browser, while the server side is running in the built-in Web server. In addition, with this architecture, the client and the server interact with classic HTTP requests. You can check this by analyzing the network traffic with the developer tools of your browser.

Registering the Blazor WASM App with Auth0

Now that you have the WebAssembly version of the Quiz Manager application, learn how to secure it. You will use Auth0 since it provides an easy way to integrate authentication and authorization without having to deal with the complexity of the underlying technology. To use Auth0, you need to provide some information and configure your application to make the two parties communicate with each other. If you don't have an Auth0 account yet, you can sign up for a free one right now.

Try out the most powerful authentication platform for free.Get started →

After accessing the Auth0 Dashboard, move to the Applications section, and follow these steps:

  1. Click the Create Application button.
  2. Provide a friendly name for your application (for example, Quiz Blazor WASM Client) and select Single Page Web Applications as the application type.
  3. Finally, click the Create button.

After you create the application, move to the Settings tab and take note of your Auth0 Domain and your Client ID. Then, assign the value https://localhost:5001/authentication/login-callback to the Allowed Callback URLs field and the value https://localhost:5001 to the Allowed Logout URLs field.

The first value tells Auth0 which URL to call back after users authenticate. The second value tells Auth0 which URL users should be redirected to after they logout.

Finally, click the Save Changes button to apply them.

Adding Support for Authentication

Now, you need to configure your Blazor project by applying some changes to make it aware of Auth0.

Configure your Blazor app

So, move to the Client/wwwroot folder and create an appsettings.json file with the following content:

{
  "Auth0": {
    "Authority": "https://<YOUR_AUTH0_DOMAIN>",
    "ClientId": "<YOUR_CLIENT_ID>"
  }
}

Replace the placeholders <YOUR_AUTH0_DOMAIN> and <YOUR_CLIENT_ID> with the respective values taken from the Auth0 dashboard.

Add support for authentication

Now, add the authentication package to the Blazor client project by running the following command in the Client folder:

dotnet add package Microsoft.AspNetCore.Components.WebAssembly.Authentication

After adding the package, still in the Client folder, edit the Program.cs file by replacing its content with the following C# code:

// Client/Program.cs

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace QuizManagerClientHosted.Client
{
  public class Program
  {
    public static async Task Main(string[] args)
    {
      var builder = WebAssemblyHostBuilder.CreateDefault(args);
      builder.RootComponents.Add<App>("#app");

      builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

      builder.Services.AddOidcAuthentication(options =>
      {
        builder.Configuration.Bind("Auth0", options.ProviderOptions);
        options.ProviderOptions.ResponseType = "code";
      });

      await builder.Build().RunAsync();
    }
  }
}

You added the call to AddOidcAuthentication() with specific options. In particular, you specified to use the parameters from the Auth0 section of the appsettings.json configuration file. Also, you specified the type of authentication and authorization flow you want to use; in this specific case, the Authorization Code flow is recommended.

To complete the implementation of authentication support in your application, open the index.html file under the Client/wwwroot folder and add the reference to the AuthenticationService.js script as shown below:

<!-- Client/wwwroot/index.html -->
<!DOCTYPE html>
<html>
  <!-- existing markup -->
  <body>
    <!-- existing markup -->
    <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
    <!--👆 new addition -->
    <script src="appsettings.jsons_framework/blazor.webassembly.js"></script>
  </body>
</html>

This script is responsible for performing the authentication operations on the WebAssembly client side.

"Blazor WebAssembly has now integrated support for authentication."

Tweet

Tweet This

Adjust the UI of your Blazor app

At this point, you prepared the infrastructure for your Blazor app to support authentication. Now you need to make some changes to the UI.

The first step is to enable support for the authorization Razor components. So, open the _Imports.razor file in the Client folder and add a reference to the Microsoft.AspNetCore.Components.Authorization and Microsoft.AspNetCore.Authorization namespaces. The content of that file will look as follows:

@* Client/_Imports.razor *@

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization  //👈 new addition
@using Microsoft.AspNetCore.Authorization             //👈 new addition
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using QuizManagerClientHosted.Client
@using QuizManagerClientHosted.Client.Shared

Then, open the App.razor file in the same folder and replace its content with the following:

<!-- Client/App.razor -->

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <Authorizing>
                    <p>Determining session state, please wait...</p>
                </Authorizing>
                <NotAuthorized>
                    <h1>Sorry</h1>
                    <p>You're not authorized to reach this page. You need to log in.</p>
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

You used the AuthorizeRouteView Blazor component to customize the content according to the user's authentication status. The CascadingAuthenticationState component will propagate the current authentication state to the inner components so that they can work on it consistently.

The next step is to create a new Razor component that allows the user to log in and to see their name when authenticated. So, create a new file named AccessControl.razor in the Client/Shared folder with the following content:

@* Client/Shared/AccessControl.razor *@

@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity.Name!
        <a href="#" @onclick="BeginSignOut">Log out</a>
    </Authorized>
    <NotAuthorized>
        <a href="authentication/login">Log in</a>
    </NotAuthorized>
</AuthorizeView>

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

The component uses the AuthorizeView component to show different content according to the user's authentication status. Basically, it shows the Log in link when the user is not authenticated. It shows the name of the user and the Log out link when the user is authenticated.

Note the URL the user is redirected to when they click the Log out link (authentication/logout). You will learn about that URL in a moment.

Now, open the MainLayout.razor file in the Shared folder and add the AccessControl component just before the About link. The final code should look like the following:

@* Client/Shared/MainLayout.razor *@

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <div class="main">
        <div class="top-row px-4">
            <AccessControl />    //👈 new addition
            <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
        </div>

        <div class="content px-4">
            @Body
        </div>
    </div>
</div>

When you registered your Blazor app with Auth0, you specified a few URLs in the form https://localhost:5001/authentication/* as allowed URLs for login callback and logout. You also used the logout URL in the AccessControl component.

To manage these URLs, you need to implement a page responsible for handling different authentication stages. For this purpose, create a new Authentication.razor file in the Pages folder with the following code:

@* Client/Pages/Authentication.razor *@

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Configuration

@inject NavigationManager Navigation
@inject IConfiguration Configuration

<RemoteAuthenticatorView Action="@Action">
    <LogOut>
        @{
            var authority = (string)Configuration["Auth0:Authority"];
            var clientId = (string)Configuration["Auth0:ClientId"];

             Navigation.NavigateTo($"{authority}/v2/logout?client_id={clientId}");
        }
    </LogOut>
</RemoteAuthenticatorView>

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

As you can see, this component implements a page containing the RemoteAuthenticatorView component. This component manages the users' authentication status and interacts with the authorization server on the Auth0 side. While the login interaction doesn't require any specific code, you need to manage the logout transaction. In fact, by design Blazor clears your authentication state on the client side but doesn't disconnect you from Auth0. To close your session on the Auth0 side, you need to explicitly call the logout endpoint, as shown in the code above.

Disclaimer: At the time of writing, the logout function seems not to be stable due to an apparently Blazor issue. In fact, in some random circumstances, it doesn't actually call the Auth0 logout endpoint.

Finally, you need to add the Authorize attribute to the QuizViewer.razor page to protect it from unauthorized accesses. Open the QuizViewer.razor file in the Pages folder and add the attribute as shown below:

@* Client/Pages/QuizViewer.razor *@

@page "/quizViewer"
@attribute [Authorize]            //👈 new addition

@using QuizManagerClientHosted.Shared

// ... exisiting code ...

Note that the presence of the Authorize attribute on the page doesn't prevent the client from calling the API on the server. You need to protect the API on the server side as well.

At this point, you can stop your Blazor app, if it is still running, and restart it to test the authentication integration. Once the app is running, by clicking the Quiz menu item, you should see the following screen:

Blazor app and the unauthenticated user

Note the Log In in the upper right corner. By clicking on it, the Auth0 Universal Login page is shown, and the authentication process takes place. After authentication, you will be able to access the QuizViewer page.

Securing the API with Auth0

The data shown in the QuizViewer page are loaded from the /quiz API implemented in the server project. This API is not protected, so any client could access it. In fact, the Blazor WASM client is able to access it without any problem. However, in a production-ready scenario, you need to protect the API to prevent unauthorized access. Although the API security implementation is out of the scope of this tutorial, you need to perform a few changes to the API in the server project to secure it.

If you want to learn more about protecting Web APIs in .NET Core, please check out this article.

Register the API

Similarly to what you did with the Blazor WASM application, you need to register the API with Auth0. So, head your browser to the Auth0 Dashboard, move to the API section, and follow these steps:

  1. Click the Create API button.
  2. Provide a friendly name for your API (for example, Quiz API) and a unique identifier (also known as audience) in the URL format (for example, https://quizapi.com).
  3. Leave the signing algorithm to RS256 and click the Create button.

This way, Auth0 is aware of your Web API and will allow you to control access.

Protecting the API

In the server project under the Server folder, open the appsettings.json. Its content look like the following:

{
  "Logging": {
      "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
      }
    },
  "AllowedHosts": "*",
  "Auth0": {
    "Authority": "https://<YOUR_AUTH0_DOMAIN>",
    "ApiIdentifier": "<YOUR_API_IDENTIFIER>"
  }
}

Replace the <YOUR_AUTH0_DOMAIN> placeholder with the Auth0 domain value you used for the Blazor WASM client. Also, replace the <YOUR_API_IDENTIFIER> placeholder with the unique identifier you defined for your API in the Auth0 Dashboard: it should be https://quizapi.com, if you kept the suggested value.

Still in the Server folder, run the following command to install the library that will handle the authorization process:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Then, open the Startup.cs file and apply the changes show below:

// Server/Startup.cs

// ... exisiting code ...
using Microsoft.AspNetCore.Authentication.JwtBearer;

// ... exisiting code ...

    public void ConfigureServices(IServiceCollection services)
    {
      services.AddAuthentication(options =>
      {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
      }).AddJwtBearer(options =>
      {
        options.Authority = Configuration["Auth0:Authority"];
        options.Audience = Configuration["Auth0:ApiIdentifier"];
      });

      services.AddControllersWithViews();
      services.AddRazorPages();
    }

// ... exisiting code ...

You added the reference to the Microsoft.AspNetCore.Authentication.JwtBearer namespace and added the statements that configure the server to handle the authorization process through Auth0.

Now, in the same Startup.cs file, add the statements highlighted below to the body of the Configure() method:

// Server/Startup.cs

// ... exisiting code ...

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      //...
      app.UseRouting();

      app.UseAuthentication();  //👈 new addition
      app.UseAuthorization();   //👈 new addition

      app.UseEndpoints(endpoints =>
      {
        endpoints.MapRazorPages();
        endpoints.MapControllers();
        endpoints.MapFallbackToFile("index.html");
      });
    }

// ... exisiting code ...

Finally, open the QuizController.cs file in the Controllers folder and apply the following changes:

// Server/Controllers/QuizController.cs

using QuizManagerClientHosted.Shared;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;  //👈 new addition

namespace QuizManagerClientHosted.Server.Controllers
{
  [ApiController]
  [Route("[controller]")]
  [Authorize]            //👈 new addition
  public class QuizController : ControllerBase
  {
    // ... existing code ...
  }
}

You added the reference to the Microsoft.AspNetCore.Authorization namespace and decorated the QuizController class with the Authorize attribute.

Remember: if you want to learn in depth how to protect your API, read this article.

Now your API is protected. To check if everything is working as expected, move to the root of the project and restart it. Then, log in to the application and click the Quiz menu item. This time you shouldn't be able to display the quiz data. Your screen should be like the following:

Blazor app unauthorized to access the API

If you take a look at the network section of your browser's developer tool, you will find that the call to the /quiz endpoint gets an HTTP 401 status code, as in the following example:

Unauthorized error when calling an API

This confirms that the server prevents unauthorized access to the API.

"Learn how to call a protected API with Blazor WebAssembly."

Tweet

Tweet This

Calling the Protected API

To enable your Blazor WASM application to access the protected API, you need to get an access token from Auth0 and provide it along with your API call. You might think to write some code that attaches this token when you make an HTTP request to the server. However, you can centralize the access token attachment to your API calls in a straightforward way.

Start by moving to the Client folder and installing the Microsoft.Extensions.Http package with the following command:

dotnet add package Microsoft.Extensions.Http

This package allows you to create named HTTP clients and customize their behavior. In your case, you will create an HTTP client that automatically attaches an access token to each HTTP request.

Open the Program.cs file and add a reference to the Microsoft.AspNetCore.Components.WebAssembly.Authentication as shown below:

// Client/Program.cs

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//👇 new addition
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

// ... existing code ...

In the Main() method, add the statements pointed out in the following code snippet:

// Client/Program.cs

// ... existing code ...

    public static async Task Main(string[] args)
    {
      var builder = WebAssemblyHostBuilder.CreateDefault(args);
      builder.RootComponents.Add<App>("#app");
      
      //👇 new addition
      builder.Services.AddHttpClient("ServerAPI", 
            client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
          .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

      builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
        .CreateClient("ServerAPI"));
      //👆 new addition
      
            // ... existing code ...
    }

// ... existing code ...

The AddHttpClient() method defines a named HttpClient instance (ServerAPI) with the current server's address as the base address to use when requesting a resource. Also, the BaseAddressAuthorizationMessageHandler class is added to the HttpClient instance as the HTTP message handler. This class is provided by the Microsoft.AspNetCore.Components.WebAssembly.Authentication namespace and is responsible for attaching the access token to any HTTP request to the application's base URI.

The actual HttpClient instance is created by the CreateClient() method of the IHttpClientFactory service implementation.

After this global configuration, you can call the quiz endpoint of your Web API. So, open the QuizViewer.razor file in the Client\Pages folder and change its content as follows:

@* Client/Pages/QuizViewer.razor *@

@page "/quizViewer"
@attribute [Authorize]

@using QuizManagerClientHosted.Shared
//👇 new addition
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http
//👆 new addition

// ... existing code ...
  
@code {
  
    // ... existing code ...
  
    protected override async Task OnInitializedAsync()
    {
        try
        {
            quiz = await Http.GetFromJsonAsync<List<QuizItem>>("quiz");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
  
  // ... existing code ...
}

You imported the Microsoft.AspNetCore.Components.WebAssembly.Authentication namespace and injected the HttpClient service created before. Then, you simply arranged the OnInitializedAsync() method by wrapping it with a try-catch statement.

After applying these changes, restart your application, log in, and try to move to the Quiz page. Unfortunately, you still will get an HTTP 401 status code, but with a different message, as you can see from the following picture:

Blazor error with invalid token message

Why does this happen? Yet we are passing the access token, as you can see from the picture above. However, the error message is saying that the access token is not valid.

Unfortunately, at the time of writing, Blazor WASM has a limitation, as documented by this issue. In a nutshell, in a standard authentication and authorization flow, you should be able to send the unique identifier of your API (the ApiIdentifier parameter, also known as the audience parameter) when you authenticate. This way, the Auth0 authorization server will give you an access token that grants you specific access to that API. Due to that limitation, your Blazor client is not explicitly requesting access to your API.

When Auth0 is not receiving the audience parameter, it emits an opaque and generic access token, as per specs. This access token is not in JWT format and doesn't contain any information about the resource the token authorizes to access.

The good news is that this issue will be resolved in .NET 6.

Defining a default audience

To overcome the current Blazor WASM limitation, you can define a default audience for your Auth0 tenant.

So, access your Auth0 Dashboard and navigate to your tenant settings. Now, scroll down to the API Authorization Settings section and add your ApiIdentifier as you default API audience, as you can see in the following picture:

Configuring the default audience in Auth0

Click the Save button to confirm your changes.

From now on, any client requiring an access token without specifying any audience parameter while authenticating will receive an access token related to your default audience.

Since, in general, the default audience is not bound to a specific API, you should use a generic name.

Anyway, consider that, whenever possible, you should use specific audiences for your APIs.

Now, log out your Blazor app and login again to get a new access token. This time you should be able to access your protected API and show the Quiz page.

Recap

This tutorial guided you in creating and securing a Blazor WebAssembly application and integrating it with Auth0. You learned how to enable your application to support authentication and call a protected API passing the access token. You also came across the Blazor issue that currently prevents your application from requesting an access token for a specific API. You then learned how to work around this limitation by configuring a default audience in your Auth0 Dashboard.

The full source code of the application secured in this tutorial can be downloaded from this GitHub repository.

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon