8

Authentication and Authorization

In this chapter, we will learn how to add authentication and authorization to our blog because we don’t want just anyone to be able to create or edit blog posts.

Covering authentication and authorization could take a whole book, so we will keep things simple here. This chapter aims to get the built-in authentication and authorization functionalities working, building on the already existing functionality that’s built into ASP.NET. That means that there is not a lot of Blazor magic involved here; many resources already exist that we can take advantage of.

Almost every system today has some way to log in, whether it is an admin interface (like ours) or a member login portal. There are many different login providers, such as Google, Twitter, and Microsoft. We can use all of these providers since we will just be building on existing architecture.

Some sites might already have a database for storing login credentials, but for our blog, we will use a service called Auth0 to manage our users. It is a very powerful way to add many different social providers (if we want to), and we don’t have to manage the users ourselves.

We can check the option to add authentication when creating our project. The authentication works differently when it comes to Blazor Server and Blazor WebAssembly, which we will look at in more detail in this chapter.

We will cover the following topics in this chapter:

  • Setting up authentication
  • Securing Blazor Server
  • Securing Blazor WebAssembly
  • Securing the API
  • Adding authorization

Technical requirements

Make sure you have followed the previous chapters or use the Chapter07 folder as a starting point.

You can find the source code for this chapter’s end result at https://github.com/PacktPublishing/Web-Development-with-Blazor-Second-Edition/tree/main/Chapter08.

Setting up authentication

There are a lot of built-in functionalities when it comes to authentication. The easiest way to add authentication is to select an authentication option when creating a project.

We need to implement authentication separately for the Blazor Server project and the Blazor WebAssembly project because they work differently.

But there are still things we can share between these two projects. First, we need to set up Auth0.

Auth0 is a service that can help us with handling our users. There are many different services like this, but Auth0 is the one that seems to be a very good service to use. We can connect one or many social connectors, which will allow our users to log in with Facebook, Twitter, Twitch, or whatever we add to our site.

Even though all of this can be achieved by writing code ourselves, integration like this is a great way to add authentication fast and also get a very powerful solution. Auth0 is free for up to 7,000 users (which our blog probably won’t reach, especially not the admin interface).

It also has great functionality to add data to our users that we have access to. We will do that later in the chapter when we add roles to our users. You’ll need to take the following steps:

  1. Head over to https://auth0.com and create an account.
  2. Click the Create Application button.
  3. Now it’s time to name our application. Use MyBlog, for example. Then it’s time to select what kind of application type we are using. Is it a native app? Is it a Single-Page Web Application, Regular Web Application, or Machine to Machine Application?

    This depends on what version of Blazor we are going to run.

    But it won’t limit the functionality, only what we need to configure when setting up our application.

    We will start with Blazor server, which is a regular web application. But we want to be able to use the same authentication for both Blazor Server and Blazor WebAssembly, and we can do that by selecting Single Page Application.

    And if we are only making a Blazor Server Application, we should use Regular Web Application, but since we are doing both, select Single Page Web Application since this will make it possible to run both.

    Next, we will choose what technology we are using for our project. We have got Apache, .NET, Django, Go, and many other choices, but we don’t have a choice for Blazor specifically, at least not at the time of writing.

    Just skip this and click the Setting tab.

  1. Now we will set up our application. There are a couple of values we need to save and use later. You need to make sure that you write down the Domain, Client ID, and Client Secret, as we will use those in a bit.

    If we scroll down, we can change the logo, but we will skip that.

  1. We need to set up the Application Login URI, our application URL (localhost for now), and the port number.

    Starting with .NET 6, the port numbers are random, so make sure you add your application’s port number:

    1. Allowed Callback URLs: https://localhost:PORTNUMBER/callback
    2. Allowed Logout URLs: https://localhost:PORTNUMBER/

    Allowed Callback URLs are the URLs Auth0 will make a call to after the user authentication and Allowed Logout URLs are where the user should be redirected after logout.

    Now press Save Changes at the bottom of the page.

Configuring Blazor Server

We are done with configuring Auth0. Next, we will configure our Blazor application.

There are many ways to store secrets in .NET (a file that is not checked in, Azure Key Vault, etc.). You can use the one that you are most familiar with.

We will keep it very simple and store secrets in our appsettings.json. Make sure to remember to exclude the file when you check in. You don’t check the secrets in source control.

To configure our Blazor Server project, follow these steps:

  1. In the BlazorServer project, open appsettings.json and add the following code:
    {
      "Auth0": {
        "Authority": "Get this from the domain for your application at Auth0",
        "ClientId": "Get this from Auth0 setting"
      }
    }
    

    These are the values we made a note of in the previous section.

    Blazor server is an ASP.NET site with some added Blazor functionality, which means we can use a NuGet package to get some of the functionality out of the box.

  1. In the BlazorServer project, add a reference to the NuGet package Auth0.AspNetCore.Authentication.
  2. Open Program.cs and add the following code just before WebApplication app = builder.Build();:
    builder.Services
        .AddAuth0WebAppAuthentication(options =>
        {
            options.Domain = builder.Configuration["Auth0:Authority"]??"";;
            options.ClientId = builder.Configuration["Auth0:ClientId"]??"";;
        });
    
  3. Also, add the following code just after app.UseRouting();. This code will allow us to secure our site:
    app.UseAuthentication();
    app.UseAuthorization();
    
  4. Add the following using at the top of the file:
    using Auth0.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authentication.Cookies;
    
  5. Blazor Server communication is done over SignalR, and OpenID and OpenAuth rely on HTTP. This is the only thing I don’t like about Blazor, because I sometimes need to build Razor pages instead of components.

    Minimal APIs are a great way to do this by adding two get methods. This way, we don’t need to create a Razor page.

  1. In Program.cs, add the following code just before app.Run():
    app.MapGet("authentication/login", async (string redirectUri, HttpContext context) =>
    {
        var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
             .WithRedirectUri(redirectUri)
             .Build();
        await context.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
    });
    

    When our site redirects to "authentication/login", the minimal API endpoint will kick off the login functionality.

  1. We need to add similar functionality for logout. Add the following code below the previous endpoint from step 7:
    app.MapGet("authentication/logout", async (HttpContext context) =>
    {
        var authenticationProperties = new LogoutAuthenticationPropertiesBuilder()
             .WithRedirectUri("/")
             .Build();
        await context.SignOutAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
        await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    });
    

The configuration is all set. Now, we need something to secure.

Securing Blazor Server

Blazor uses App.razor for routing. To enable securing Blazor, we need to add a couple of components in the app component.

We need to add a CascadingAuthenticationState, which will send the authentication state to all the components that are listening for it. We also need to change the route view to an AuthorizeRouteView, which can have different views depending on whether or not you are authenticated:

  1. In the end, the App.razor component should look like this:
    <CascadingAuthenticationState>
        <Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(Components.Pages.Index).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>
                <FocusOnNavigate RouteData="@routeData" Selector="h1" />
            </Found>
            <NotFound>
                <PageTitle>Not found</PageTitle>
                <LayoutView Layout="@typeof(MainLayout)">
                    <p role="alert">Sorry, there's nothing at this address.</p>
                </LayoutView>
            </NotFound>
        </Router>
    </CascadingAuthenticationState>
    

    Now only two things remain, a page that we can secure and a login link display.

  1. In the Components project, add the NuGet package:

    Microsoft.AspNetCore.Components.Authorization

  1. Open _Imports.razor and add the namespaces:
    @using Microsoft.AspNetCore.Components.Authorization 
    @using Components.RazorComponents
    
  2. In the RazorComponents folder, add a new interface called ILoginStatus and replace the content with:
    namespace Components.RazorComponents;
    public interface ILoginStatus
    {
    }
    
  3. In the RazorComponents folder, add a new razor component called LoginStatus.razor.

    Replace the content with:

    @implements ILoginStatus
    <AuthorizeView>
        <Authorized>
            <a href="authentication/logout">Log out</a>
        </Authorized>
        <NotAuthorized>
            <a href="authentication/login?redirectUri=/">Log in</a>
        </NotAuthorized>
    </AuthorizeView>
    

    LoginStatus is a component that will show a login link if we are not authenticated and a logout link if we are authenticated.

    The code above is the Blazor Server implementation of that control. For Blazor WebAssembly, we need to change the component just a bit, but since all the components, including the layout, are in a shared library, it’s not entirely easy to do.

    Here is where the DynamicComponent component can help us. It makes it possible to load a component using a type or a string. We will solve this by dependency-injecting the component type we want the MainLayout to use.

  1. Open Shared/MainLayout.razor and add the following:
    @inject ILoginStatus status
    

    We are injecting a component of the type LoginStatus; another way would be to create an interface and use it instead, but to keep it simple, let’s use the LoginStatus component for now.

  1. Replace the about link with:
    <DynamicComponent Type="@status.GetType()"/>
    

    So, based on what type of component the dependency injection returns to us, it will render that component.

    In the next section, we will also create one for Blazor WebAssembly.

  1. Add the authorize attribute to the component we wish to secure. The choices are:

    Pages/Admin/BlogPostEdit.razor

    Pages/Admin/BlogPostList.razor

    Pages/Admin/CategoryList.razor

    Pages/Admin/TagList.razor

    Add the following attribute to all of them:

    @attribute [Authorize]
    
  1. In the BlazorServer project, in the file Program.cs, add the following line:
    builder.Services.AddTransient<ILoginStatus,LoginStatus>();
    
  2. Add the following namespaces:
    using Components.RazorComponents;
    

    The dependency injection will return an instance of an ILoginStatus and we will get the LoginStatus class.

    This is all it takes, some configuration, and then we are all set.

    Now set the BlazorServer-project as a startup project and see if you can access the /admin/blogposts page (spoiler: you shouldn’t be able to); log in (create a user), and see if you can access the page now.

    Our admin interface is secured.

In the next section, we will secure the Blazor WebAssembly version of our blog and the API.

Securing Blazor WebAssembly

The WebAssembly project has some of the same functionalities; it is a bit more complicated because it requires API authentication, but we will start with securing the client.

By default (if we choose to add authentication when we create the project), it will use IdentityServer to authenticate both the client and the API.

We will use Auth0 for this instead, the same application we created earlier in this chapter:

  1. In the BlazorWebAssembly.Client project, in the wwwroot folder, add a new JSON file called appsettings.json.

    The appsettings file will automatically be picked up by Blazor.

  1. Add the following JSON:
    {
        "Auth0": {
            "Authority": "Get this from the domain for your application from Auth0",
            "ClientId": "Get this from Auth0 setting"
        }
    }
    

    Replace the values with the same ones we did with the Blazor Server project – the values from the Setting up Authentication section.

    Make sure to add https:// at the beginning of the Authority (this is not needed in the Blazor Server project).

  1. Add the following NuGet packages:
    Microsoft.AspNetCore.Components.WebAssembly.Authentication
    Microsoft.Extensions.Http
    
  2. For us to be able to access our API, we need to set up an HttpClient.

    In Program.cs, add the following lines:

    builder.Services.AddHttpClient("Public",
        client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
    builder.Services.AddHttpClient("Authenticated", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
      .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
    

    We will create one for getting requests (non-authenticated calls) called public, and one for authenticated calls called authenticated.

    These are the names we used in Chapter 7, Creating an API.

  1. Add the following namespaces:
    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    using Data;
    
  2. We also need to set up dependency injection so that when we ask for an IBlogAPI, we will get the BlogApiWebClient that we created in Chapter 7, Creating and API.

    In Program.cs, add the following code:

    builder.Services.AddTransient<IBlogApi, BlogApiWebClient>();
    
  1. Now it’s time to configure the authentication. Add the following code:
    builder.Services.AddOidcAuthentication(options =>
    {
        builder.Configuration.Bind("Auth0", options.ProviderOptions);
        options.ProviderOptions.ResponseType = "code";
    });
    

    We are getting the configuration from our appsettings.json file. In this case, we are using built-in functionality in .NET instead of using a library that Auth0 has provided for us.

  1. In wwwroot/index.html, we need to add a reference to JavaScript.
  2. Just above the </body> tag, add this JavaScript:
    <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
    

    It will handle any authentication logic on the client.

Great, our app is configured. Next, let’s secure it.

Now everything is prepared for us to secure our WebAssembly app.

This process is pretty much the same as for the Blazor server, but we need to implement it a bit differently.

We need to add a CascadingAuthenticationState, which will send the authentication state to all the components that are listening for it. We also need to change the route view to an AuthorizeRouteView, which can have different views depending on whether or not you are authenticated:

  1. In the BlazorWebAssembly.Client project, in the end, the App.razor component should look like this:
    @using Microsoft.AspNetCore.Components.Authorization
    <CascadingAuthenticationState>
        <Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(Components.Pages.Index).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>
                <FocusOnNavigate RouteData="@routeData" Selector="h1" />
            </Found>
            <NotFound>
                <PageTitle>Not found</PageTitle>
                <LayoutView Layout="@typeof(MainLayout)">
                    <p role="alert">Sorry, there's nothing at this address.</p>
                </LayoutView>
            </NotFound>
        </Router>
    </CascadingAuthenticationState>
    

    Now only two things remain, a page that we can secure and a login link display.

  1. Add the NuGet package:

    Microsoft.AspNetCore.Components.WebAssembly.Authentication

  1. Open _Imports.razor and add the namespace:
    @using Microsoft.AspNetCore.Components.Authorization 
    @using Microsoft.AspNetCore.Authorization 
    @using Components.RazorComponents;
    @using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
  2. Add a new Razor component called LoginStatusWasm.razor. This is the same component we created in our shared library, but this one is specific for WebAssembly.
  3. Replace the content with:
    @implements ILoginStatus
    @inject NavigationManager Navigation
    <AuthorizeView>
        <Authorized>
            <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)
        {
            Navigation.NavigateToLogout("authentication/logout");
        }
    }
    
  4. The implementation uses an extension method, NavigateToLogout. LoginStatusWasm is a component that will show a login link if we are not authenticated and a logout link if we are authenticated.

    We also have a route called authentication that we will implement in just a bit.

    The LoginStatusWasm will be injected using dependency injection, just like we did with the Blazor Server implementation.

  1. In the Program.cs, add the following line:
    builder.Services.AddTransient<ILoginStatus, LoginStatusWasm>();
    

    When our MainLayout is rendered, it will get an instance of LoginStatusWasm and render that component.

    Now it’s time to implement the authentication route. Create a new Razor component called Authentication.razor and add the following code:

    @page "/authentication/{action}"
    @inject NavigationManager Navigation
    @inject IConfiguration Configuration
    <RemoteAuthenticatorView Action="@Action">
        <LogOut>
            @{
                var authority = Configuration["Auth0:Authority"]??string.Empty;
                var clientId = Configuration["Auth0:ClientId"]?? string.Empty;
                Navigation.NavigateTo($"{authority}/v2/logout?client_id={clientId}");
            }
        </LogOut>
    </RemoteAuthenticatorView>
    @code{
        [Parameter] public string Action { get; set; } = "";
    }
    

    It uses a built-in component called RemoteAuthenticatorView. It makes the necessary calls and also makes sure to protect us from cross-site calls.

    The call to await SignOutManager.SetSignOutState(); that we added in our LoginStatusWasm component will set a state that will be checked in the RemoteAuthenticatorView.

    It will then make a call to Auth0 to log out the client.

We now have secured our WebAssembly project. We also need to secure the pages we want to protect, but since they are in the Components project, they are already secured since we did that in the Securing Blazor Server section.

Adjusting Auth0

We also need to update the Auth0 Allowed Logout URLs and Allowed Callback URLs as follows:

  1. Log in to Auth0, click Applications, and select the application.
  2. Add a new URL to Allowed Callback URLs add (with a comma separating the URLs):

    https://localhost:PORTNUMBER/authentication/login-callback

    Note: this port number is something else (not the same port we added earlier).

    In my case it looks like this:

    https://localhost:7174/callback, https://localhost:7276/authentication/login-callback

  1. In the Allowed Logout URLs box, add the following to the beginning of the string (the WASM URL needs to go first):

    https://localhost:PORTNUMBER

    In my case, it looks like this: https://localhost:7276/, https://localhost:7174/

Set the BlazorWebAssembly.Server project as our startup project and run.

We should now be able to click Login in the top right corner, log in, and you will end up with a logout link in the top left corner.

If we navigate to /admin/blogposts, we will see a list of blog posts if we are authenticated; if we are not, we will see a message saying: Sorry, You’re not authorized to reach this page. You need to log in.

Fantastic! Our pages are secure, but our API is still wide open. We need to secure the API using the same login mechanism used to secure the client.

Securing the API

When working with Blazor WebAssembly, we need a central place that handles authentication since we need to authenticate both the client and use the same authentication for the API.

Auth0 has support for APIs as well.

Configure Auth0

To secure our API, we need to let Auth0 know about the API:

  1. Log in to Auth0, click Applications, then click APIs, and then click Create API.
  2. Add a name, MyBlogAPI, and add an identifier, https://MyBlogApi. This is what we will later use as Audience; Auth0 will never call this URL.

    Leave Signing Algorithm as is.

  1. Click Create.

Our authentication for our API is all done; next, we will limit access inside the API.

Configure the API

Now we need to configure the API:

  1. In the BlazorWebAssembly.Server project, add the NuGet package:

    Microsoft.AspNetCore.Authentication.JwtBearer

  1. Open Program.cs and add the using statement:
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    
  2. Add the following code just above var app=builder.Build;:
    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, c =>
        {
            c.Authority = builder.Configuration["Auth0:Authority"];
            c.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
            {
                ValidAudience = builder.Configuration["Auth0:Audience"],
                ValidIssuer = builder.Configuration["Auth0:Authority"]
            };
        });
    builder.Services.AddAuthorization();
    
  3. Just under app.UseRouting();, add:
    app.UseAuthentication();
    app.UseAuthorization();
    
  4. Open appsettings.json and add:
      "Auth0": {
        "Authority": "Get this value from the Domain in Auth0 application settings",
        "Audience": "Get this value from the Identifier in Auth0 API settings"
      }
    

    Replace the placeholder with values from Auth0. Make sure to add https:// at the beginning of the Authority.

  1. In the BlazorWebAssembly.Client project, we now need to add the audience. Open wwwroot/appsettings.json in the Auth0 JSON object, and add:
      "Audience": "Get this value from the Identifier in Auth0 API settings"
    
  2. In Program.cs, inside the AddOidcAuthentication method call, add:
    options.ProviderOptions.AdditionalProviderParameters.Add("audience", builder.Configuration["Auth0:Audience"]);
    

    That should be all it takes to configure the API.

    Set BlazorWebAssembly.Server as the startup project and run the project. You should now be able to log in, add blog posts, and manage tags and categories.

But what if different users have different permissions?

That is where roles come in.

Adding roles

Blazor Server and Blazor WebAssembly handle roles a bit differently; it’s nothing major but we need to do different implementations.

Configuring Auth0 by adding roles

Let’s start by adding roles in Auth0:

  1. Log in to Auth0, navigate to User Management, Roles, and click Create Role.
  2. Enter the name Administrator and the description Can do anything and press Create.
  3. Go to the Users tab, click Add Users, and search for your user and click Assign. You can also manage roles from the Users menu to the left.
  4. By default, roles won’t be sent to the client, so we need to enrich the data to include roles.

    We do that by adding an action.

  1. Go to Actions, and then Flows.

    Flows are a way to execute code in a particular flow.

    We want Auth0 to add our roles when we log in.

  1. Select Login, and there we will see the flow; in our case, we don’t have anything yet.
  2. On the right-hand side, click Custom and the plus sign. As a small pop-up menu appears, select Build Custom.
  3. Name the action Add Roles, leave Trigger and Runtime as is, and press Create.

    We will see a window where we can write our action.

  1. Replace all the code with the following:
    /**
     * @param {Event} event - Details about the user and the context in which they are logging in.
     * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
     */
    exports.onExecutePostLogin = async (event, api) => {
      const claimName  = 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role'
      if (event.authorization) {
        api.idToken.setCustomClaim(claimName, event.authorization.roles);
        api.accessToken.setCustomClaim(claimName, event.authorization.roles);
      }
    }
    
  2. Click Deploy and then Back to flow.
  3. Click Custom again, and we will see our newly created action.
  4. Drag the Add Roles action to the arrow between Start and Complete.
  5. Click Apply.

Now we have an action that will add the roles to our login token.

Our user is now an administrator. It’s worth noting that roles are a paid feature in Auth0 and will only be free during the trial.

Now let’s set up Blazor Server to use this new role.

Adding roles to Blazor Server

Since we are using the Auth0 library the setup is almost done for Blazor Server.

Let’s modify a component to show if the user is an administrator:

  1. In the Components project, open Shared/NavMenu.razor:

    At the top of the component, add:

    <AuthorizeView Roles="Administrator">
        <Authorized>
            Hi admin!
        </Authorized>
        <NotAuthorized>
            You are not an admin =(
        </NotAuthorized>
    </AuthorizeView>
    

    Set BlazorServer as a startup project and run it.

    If we log in, we should be able to see text to the left saying, Hi Admin! in black text on top of dark blue, so it might not be very visible. We will take care of this in Chapter 9, Sharing Code and Resources.

Next, we will add the same roles to the BlazorWebAssembly project.

Adding roles to Blazor WebAssembly

Adding roles to Blazor WebAssembly is almost as easy. There is one challenge we need to fix first.

When we get the roles from Auth0, we get them as an array, but we need to split them up into separate objects, and to do that, we need to create a class that does that for us:

  1. In the BlazorWebAssembly.Client project, create a new class called ArrayClaimsPrincipalFactory.cs.
  2. Replace the code with the following:
    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
    using System.Security.Claims;
    using System.Text.Json;
    namespace BlazorWebAssembly.Client;
    public class ArrayClaimsPrincipalFactory<TAccount> : AccountClaimsPrincipalFactory<TAccount> where TAccount : RemoteUserAccount
    {
        public ArrayClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
        { }
        public async override ValueTask<ClaimsPrincipal> CreateUserAsync(TAccount account, RemoteAuthenticationUserOptions options)
        {
            var user = await base.CreateUserAsync(account, options);
            var claimsIdentity = (ClaimsIdentity?)user.Identity;
            if (account != null)
            {
                foreach (var kvp in account.AdditionalProperties)
                {
                    var name = kvp.Key;
                    var value = kvp.Value;
                    if (value != null && (value is JsonElement element && element.ValueKind == JsonValueKind.Array))
                    {
                        claimsIdentity?.RemoveClaim(claimsIdentity.FindFirst(kvp.Key));
                        var claims = element.EnumerateArray()
                            .Select(x => new Claim(kvp.Key, x.ToString()));
                        claimsIdentity?.AddClaims(claims);
                    }
                }
            }
            return user;
        }
    }
    

    The class checks if the roles we got back are in an array, and if so, splits them up into multiple entries.

    In the Git repo, there is a page in the components project showing the roles if you would like to dig deeper (Pages/AuthTest.razor).

  1. In Program.cs, add the following just after the call to AddOidcAuthentication:
    .AddAccountClaimsPrincipalFactory<ArrayClaimsPrincipalFactory <RemoteUserAccount>>();
    

    In the end, it should look something like this:

    builder.Services.AddOidcAuthentication(options =>
    {
        //Removed for brevity
    }).AddAccountClaimsPrincipalFactory<ArrayClaimsPrincipalFactory <RemoteUserAccount>>();
    

Set BlazorWebAssembly.Server as a startup project and run it. If we log in, we should be able to see text to the left saying Hi Admin! in black text on top of dark blue, so it might not be very visible. We will take care of this in Chapter 9, Sharing Code and Resources.

Awesome! We have authentication and authorization working for both Blazor Server and Blazor WebAssembly and secured our API!

Summary

In this chapter, we learned how to add authentication to our existing site. It is easier to add authentication at the point of creating a project, but now we have a better understanding of what is going on under the hood and how to handle adding an external source for authentication.

Throughout the book, we have been sharing components between the two projects.

In the next chapter, we will look at sharing even more things like static files and CSS and try to make everything look nice.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset