Secure a hosted ASP.NET Core Blazor WebAssembly app with Identity Server

This article explains how to create a hosted Blazor WebAssembly solution that uses Duende Identity Server to authenticate users and API calls.

For more information on solutions, see Tooling for ASP.NET Core Blazor.

Important

Duende Software might require you to pay a license fee for production use of Duende Identity Server. For more information, see Migrate from ASP.NET Core 5.0 to 6.0.

Note

To configure a standalone or hosted Blazor WebAssembly app to use an existing, external Identity Server instance, follow the guidance in Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library.

To create a new Blazor WebAssembly project with an authentication mechanism:

  1. Create a new project.

  2. Choose the Blazor WebAssembly App template. Select Next.

  3. Provide a Project name without using dashes (see the following WARNING). Confirm that the Location is correct. Select Next.

    Warning

    Avoid using dashes (-) in the project name that break the formation of the OIDC app identifier. Logic in the Blazor WebAssembly project template uses the project name for an OIDC app identifier in the solution's configuration. Pascal case (BlazorSample) or underscores (Blazor_Sample) are acceptable alternatives. For more information, see Dashes in a hosted Blazor WebAssembly project name break OIDC security (dotnet/aspnetcore #35337).

  4. In the Additional information dialog, select Individual Accounts as the Authentication Type to store users within the app using ASP.NET Core's Identity system.

  5. Select the ASP.NET Core Hosted checkbox.

Server app configuration

The following sections describe additions to the project when authentication support is included.

Startup class

The Startup class has the following additions.

  • In Program.cs:

    • ASP.NET Core Identity:

      builder.Services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(
              Configuration.GetConnectionString("DefaultConnection")));
      
      builder.Services.AddDefaultIdentity<ApplicationUser>(options => 
              options.SignIn.RequireConfirmedAccount = true)
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • IdentityServer with an additional AddApiAuthorization helper method that sets up default ASP.NET Core conventions on top of IdentityServer:

      builder.Services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Authentication with an additional AddIdentityServerJwt helper method that configures the app to validate JWT tokens produced by IdentityServer:

      builder.Services.AddAuthentication()
          .AddIdentityServerJwt();
      

      Note

      When a single authentication scheme is registered, the authentication scheme is automatically used as the app's default scheme, and it isn't necessary to state the scheme to AddAuthentication or via AuthenticationOptions. For more information, see Overview of ASP.NET Core Authentication and the ASP.NET Core announcement (aspnet/Announcements #490).

  • In Program.cs:

    • The IdentityServer middleware exposes the OpenID Connect (OIDC) endpoints:

      app.UseIdentityServer();
      
    • The Authentication middleware is responsible for validating request credentials and setting the user on the request context:

      app.UseAuthentication();
      
    • Authorization Middleware enables authorization capabilities:

      app.UseAuthorization();
      

Azure App Service on Linux

Specify the issuer explicitly when deploying to Azure App Service on Linux. For more information, see Introduction to authentication for Single Page Apps on ASP.NET Core.

AddApiAuthorization

The AddApiAuthorization helper method configures Identity Server for ASP.NET Core scenarios. Identity Server is a powerful and extensible framework for handling app security concerns. IdentityServer exposes unnecessary complexity for the most common scenarios. Consequently, a set of conventions and configuration options is provided that we consider a good starting point. Once your authentication needs change, the full power of IdentityServer is available to customize authentication to suit an app's requirements.

AddIdentityServerJwt

The AddIdentityServerJwt helper method configures a policy scheme for the app as the default authentication handler. The policy is configured to allow Identity to handle all requests routed to any subpath in the Identity URL space /Identity. The JwtBearerHandler handles all other requests. Additionally, this method:

  • Registers an {APPLICATION NAME}API API resource with IdentityServer with a default scope of {APPLICATION NAME}API.
  • Configures the JWT Bearer Token Middleware to validate tokens issued by IdentityServer for the app.

WeatherForecastController

In the WeatherForecastController (Controllers/WeatherForecastController.cs), the [Authorize] attribute is applied to the class. The attribute indicates that the user must be authorized based on the default policy to access the resource. The default authorization policy is configured to use the default authentication scheme, which is set up by AddIdentityServerJwt. The helper method configures JwtBearerHandler as the default handler for requests to the app.

ApplicationDbContext

In the ApplicationDbContext (Data/ApplicationDbContext.cs), DbContext extends ApiAuthorizationDbContext<TUser> to include the schema for IdentityServer. ApiAuthorizationDbContext<TUser> is derived from IdentityDbContext.

To gain full control of the database schema, inherit from one of the available Identity DbContext classes and configure the context to include the Identity schema by calling builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) in the OnModelCreating method.

OidcConfigurationController

In the OidcConfigurationController (Controllers/OidcConfigurationController.cs), the client endpoint is provisioned to serve OIDC parameters.

App settings

In the app settings file (appsettings.json) at the project root, the IdentityServer section describes the list of configured clients. In the following example, there's a single client. The client name corresponds to the app name and is mapped by convention to the OAuth ClientId parameter. The profile indicates the app type being configured. The profile is used internally to drive conventions that simplify the configuration process for the server.

"IdentityServer": {
  "Clients": {
    "{APP ASSEMBLY}.Client": {
      "Profile": "IdentityServerSPA"
    }
  }
}

The placeholder {APP ASSEMBLY} is the app's assembly name (for example, BlazorSample.Client).

Client app configuration

Authentication package

When an app is created to use Individual User Accounts (Individual), the app automatically receives a package reference for the Microsoft.AspNetCore.Components.WebAssembly.Authentication package. The package provides a set of primitives that help the app authenticate users and obtain tokens to call protected APIs.

If adding authentication to an app, manually add the Microsoft.AspNetCore.Components.WebAssembly.Authentication package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

HttpClient configuration

In Program.cs, a named HttpClient ({APP ASSEMBLY}.ServerAPI) is configured to supply HttpClient instances that include access tokens when making requests to the server API:

builder.Services.AddHttpClient("{APP ASSEMBLY}.ServerAPI", 
        client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("{APP ASSEMBLY}.ServerAPI"));

The placeholder {APP ASSEMBLY} is the app's assembly name (for example, BlazorSample.Client).

Note

If you're configuring a Blazor WebAssembly app to use an existing Identity Server instance that isn't part of a hosted Blazor solution, change the HttpClient base address registration from IWebAssemblyHostEnvironment.BaseAddress (builder.HostEnvironment.BaseAddress) to the server app's API authorization endpoint URL.

API authorization support

The support for authenticating users is plugged into the service container by the extension method provided inside the Microsoft.AspNetCore.Components.WebAssembly.Authentication package. This method sets up the services required by the app to interact with the existing authorization system.

builder.Services.AddApiAuthorization();

By default, configuration for the app is loaded by convention from _configuration/{client-id}. By convention, the client ID is set to the app's assembly name. This URL can be changed to point to a separate endpoint by calling the overload with options.

Imports file

The Microsoft.AspNetCore.Components.Authorization namespace is made available throughout the app via the _Imports.razor file:

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}.Client
@using {APPLICATION ASSEMBLY}.Client.Shared

Index page

The Index page (wwwroot/index.html) page includes a script that defines the AuthenticationService in JavaScript. AuthenticationService handles the low-level details of the OIDC protocol. The app internally calls methods defined in the script to perform the authentication operations.

<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>

App component

The App component (App.razor) is similar to the App component found in Blazor Server apps:

  • The CascadingAuthenticationState component manages exposing the AuthenticationState to the rest of the app.
  • The AuthorizeRouteView component makes sure that the current user is authorized to access a given page or otherwise renders the RedirectToLogin component.
  • The RedirectToLogin component manages redirecting unauthorized users to the login page.

Due to changes in the framework across releases of ASP.NET Core, Razor markup for the App component (App.razor) isn't shown in this section. To inspect the markup of the component for a given release, use either of the following approaches:

  • Create an app provisioned for authentication from the default Blazor WebAssembly project template for the version of ASP.NET Core that you intend to use. Inspect the App component (App.razor) in the generated app.

  • Inspect the App component (App.razor) in reference source.

    Note

    Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

RedirectToLogin component

The RedirectToLogin component (Shared/RedirectToLogin.razor):

  • Manages redirecting unauthorized users to the login page.
  • Preserves the current URL that the user is attempting to access so that they can be returned to that page if authentication is successful.
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Options

@inject IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> Options
@code {
    protected override void OnInitialized()
    {
        Navigation.NavigateToLogin(Options.Get(
            Microsoft.Extensions.Options.Options.DefaultName)
            .AuthenticationPaths.LogInPath);
    }
}

LoginDisplay component

The LoginDisplay component (Shared/LoginDisplay.razor) is rendered in the MainLayout component (Shared/MainLayout.razor) and manages the following behaviors:

  • For authenticated users:
    • Displays the current user name.
    • Offers a link to the user profile page in ASP.NET Core Identity.
    • Offers a button to log out of the app.
  • For anonymous users:
    • Offers the option to register.
    • Offers the option to log in.

Due to changes in the framework across releases of ASP.NET Core, Razor markup for the LoginDisplay component isn't shown in this section. To inspect the markup of the component for a given release, use either of the following approaches:

  • Create an app provisioned for authentication from the default Blazor WebAssembly project template for the version of ASP.NET Core that you intend to use. Inspect the LoginDisplay component in the generated app.

  • Inspect the LoginDisplay component in reference source. The templated content for Hosted equal to true is used.

    Note

    Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Authentication component

The page produced by the Authentication component (Pages/Authentication.razor) defines the routes required for handling different authentication stages.

The RemoteAuthenticatorView component:

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

<RemoteAuthenticatorView Action="@Action" />

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

FetchData component

The FetchData component shows how to:

  • Provision an access token.
  • Use the access token to call a protected resource API in the Server app.

The @attribute [Authorize] directive indicates to the Blazor WebAssembly authorization system that the user must be authorized in order to visit this component. The presence of the attribute in the Client app doesn't prevent the API on the server from being called without proper credentials. The Server app also must use [Authorize] on the appropriate endpoints to correctly protect them.

IAccessTokenProvider.RequestAccessToken takes care of requesting an access token that can be added to the request to call the API. If the token is cached or the service is able to provision a new access token without user interaction, the token request succeeds. Otherwise, the token request fails with an AccessTokenNotAvailableException, which is caught in a try-catch statement.

In order to obtain the actual token to include in the request, the app must check that the request succeeded by calling tokenResult.TryGetToken(out var token).

If the request was successful, the token variable is populated with the access token. The AccessToken.Value property of the token exposes the literal string to include in the Authorization request header.

If the request failed because the token couldn't be provisioned without user interaction:

  • ASP.NET Core 7.0 or later: The app navigates to AccessTokenResult.InteractiveRequestUrl using the given AccessTokenResult.InteractionOptions to allow refreshing the access token.
  • ASP.NET Core 6.0 or earlier: The token result contains a redirect URL. Navigating to this URL takes the user to the login page and back to the current page after a successful authentication.
@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Run the app

Run the app from the Server project. When using Visual Studio, either:

  • Set the Startup Projects drop down list in the toolbar to the Server API app and select the Run button.
  • Select the Server project in Solution Explorer and select the Run button in the toolbar or start the app from the Debug menu.

Name and role claim with API authorization

Custom user factory

In the Client app, create a custom user factory. Identity Server sends multiple roles as a JSON array in a single role claim. A single role is sent as a string value in the claim. The factory creates an individual role claim for each of the user's roles.

CustomUserFactory.cs:

using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);

        if (user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = account.AdditionalProperties[identity.RoleClaimType];

                if (rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
                        }
                    }
                    else
                    {
                        identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
                    }
                }
            }
        }

        return user;
    }
}

In the Client app, register the factory in Program.cs:

builder.Services.AddApiAuthorization()
    .AddAccountClaimsPrincipalFactory<CustomUserFactory>();

In the Server app, call AddRoles on the Identity builder, which adds role-related services:

using Microsoft.AspNetCore.Identity;

...

services.AddDefaultIdentity<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Configure Identity Server

Use one of the following approaches:

API authorization options

In the Server app:

  • Configure Identity Server to put the name and role claims into the ID token and access token.
  • Prevent the default mapping for roles in the JWT token handler.
using System.IdentityModel.Tokens.Jwt;
using System.Linq;

...

services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Profile Service

In the Server app, create a ProfileService implementation.

ProfileService.cs:

using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using System.Threading.Tasks;

public class ProfileService : IProfileService
{
    public ProfileService()
    {
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
        context.IssuedClaims.AddRange(nameClaim);

        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
        context.IssuedClaims.AddRange(roleClaims);

        await Task.CompletedTask;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        await Task.CompletedTask;
    }
}

In the Server app, register the Profile Service in Program.cs:

using Duende.IdentityServer.Services;

...

builder.Services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Use authorization mechanisms

In the Client app, component authorization approaches are functional at this point. Any of the authorization mechanisms in components can use a role to authorize the user:

User.Identity.Name is populated in the Client app with the user's user name, which is usually their sign-in email address.

UserManager and SignInManager

Set the user identifier claim type when a Server app requires:

In Program.cs for ASP.NET Core 6.0 or later:

using System.Security.Claims;

...

builder.Services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

In Startup.ConfigureServices for versions of ASP.NET Core earlier than 6.0:

using System.Security.Claims;

...

services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

The following WeatherForecastController logs the UserName when the Get method is called:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using BlazorSample.Server.Models;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers
{
    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly UserManager<ApplicationUser> userManager;

        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", 
            "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger, 
            UserManager<ApplicationUser> userManager)
        {
            this.logger = logger;
            this.userManager = userManager;
        }

        [HttpGet]
        public async Task<IEnumerable<WeatherForecast>> Get()
        {
            var rng = new Random();

            var user = await userManager.GetUserAsync(User);

            if (user != null)
            {
                logger.LogInformation($"User.Identity.Name: {user.UserName}");
            }

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

In the preceding example:

  • The Server project's namespace is BlazorSample.Server.
  • The Shared project's namespace is BlazorSample.Shared.

Host in Azure App Service with a custom domain and certificate

The following guidance explains:

  • How to deploy a hosted Blazor WebAssembly app with Identity Server to Azure App Service with a custom domain.
  • How to create and use a TLS certificate for HTTPS protocol communication with browsers. Although the guidance focuses on using the certificate with a custom domain, the guidance is equally applicable to using a default Azure Apps domain, for example contoso.azurewebsites.net.

For this hosting scenario, do not use the same certificate for Identity Server's token signing key and the site's HTTPS secure communication with browsers:

  • Using different certificates for these two requirements is a good security practice because it isolates private keys for each purpose.
  • TLS certificates for communication with browsers is managed independently without affecting Identity Server's token signing.
  • When Azure Key Vault supplies a certificate to an App Service app for custom domain binding, Identity Server can't obtain the same certificate from Azure Key Vault for token signing. Although configuring Identity Server to use the same TLS certificate from a physical path is possible, placing security certificates into source control is a poor practice and should be avoided in most scenarios.

In the following guidance, a self-signed certificate is created in Azure Key Vault solely for Identity Server token signing. The Identity Server configuration uses the key vault certificate via the app's CurrentUser > My certificate store. Other certificates used for HTTPS traffic with custom domains are created and configured separately from the Identity Server signing certificate.

To configure an app, Azure App Service, and Azure Key Vault to host with a custom domain and HTTPS:

  1. Create an App Service plan with an plan level of Basic B1 or higher. App Service requires a Basic B1 or higher service tier to use custom domains.

  2. Create a PFX certificate for the site's secure browser communication (HTTPS protocol) with a common name of the site's fully qualified domain name (FQDN) that your organization controls (for example, www.contoso.com). Create the certificate with:

    • Key uses
      • Digital signature validation (digitalSignature)
      • Key encipherment (keyEncipherment)
    • Enhanced/extended key uses
      • Client Authentication (1.3.6.1.5.5.7.3.2)
      • Server Authentication (1.3.6.1.5.5.7.3.1)

    To create the certificate, use one of the following approaches or any other suitable tool or online service:

    Make note of the password, which is used later to import the certificate into Azure Key Vault.

    For more information on Azure Key Vault certificates, see Azure Key Vault: Certificates.

  3. Create a new Azure Key Vault or use an existing key vault in your Azure subscription.

  4. In the key vault's Certificates area, import the PFX site certificate. Record the certificate's thumbprint, which is used in the app's configuration later.

  5. In Azure Key Vault, generate a new self-signed certificate for Identity Server token signing. Give the certificate a Certificate Name and Subject. The Subject is specified as CN={COMMON NAME}, where the {COMMON NAME} placeholder is the certificate's common name. The common name can be any alphanumeric string. For example, CN=IdentityServerSigning is a valid certificate Subject. In Issuance Policy > Advanced Policy Configuration, use the default settings. Record the certificate's thumbprint, which is used in the app's configuration later.

  6. Navigate to Azure App Service in the Azure portal and create a new App Service with the following configuration:

    • Publish set to Code.
    • Runtime stack set to the app's runtime.
    • For Sku and size, confirm that the App Service tier is Basic B1 or higher. App Service requires a Basic B1 or higher service tier to use custom domains.
  7. After Azure creates the App Service, open the app's Configuration and add a new application setting specifying the certificate thumbprints recorded earlier. The app setting key is WEBSITE_LOAD_CERTIFICATES. Separate the certificate thumbprints in the app setting value with a comma, as the following example shows:

    • Key: WEBSITE_LOAD_CERTIFICATES
    • Value: 57443A552A46DB...D55E28D412B943565,29F43A772CB6AF...1D04F0C67F85FB0B1

    In the Azure portal, saving app settings is a two-step process: Save the WEBSITE_LOAD_CERTIFICATES key-value setting, then select the Save button at the top of the blade.

  8. Select the app's TLS/SSL settings. Select Private Key Certificates (.pfx). Use the Import Key Vault Certificate process. Use the process twice to import both the site's certificate for HTTPS communication and the site's self-signed Identity Server token signing certificate.

  9. Navigate to the Custom domains blade. At your domain registrar's website, use the IP address and Custom Domain Verification ID to configure the domain. A typical domain configuration includes:

    • An A Record with a Host of @ and a value of the IP address from the Azure portal.
    • A TXT Record with a Host of asuid and the value of the verification ID generated by Azure and provided by the Azure portal.

    Make sure that you save the changes at your domain registrar's website correctly. Some registrar websites require a two-step process to save domain records: One or more records are saved individually followed by updating the domain's registration with a separate button.

  10. Return to the Custom domains blade in the Azure portal. Select Add custom domain. Select the A Record option. Provide the domain and select Validate. If the domain records are correct and propagated across the Internet, the portal allows you to select the Add custom domain button.

    It can take a few days for domain registration changes to propagate across Internet domain name servers (DNS) after they're processed by your domain registrar. If domain records aren't updated within three business days, confirm the records are correctly set with the domain registrar and contact their customer support.

  11. In the Custom domains blade, the SSL STATE for the domain is marked Not Secure. Select the Add binding link. Select the site HTTPS certificate from the key vault for the custom domain binding.

  12. In Visual Studio, open the Server project's app settings file (appsettings.json or appsettings.Production.json). In the Identity Server configuration, add the following Key section. Specify the self-signed certificate Subject for the Name key. In the following example, the certificate's common name assigned in the key vault is IdentityServerSigning, which yields a Subject of CN=IdentityServerSigning:

    "IdentityServer": {
    
      ...
    
      "Key": {
        "Type": "Store",
        "StoreName": "My",
        "StoreLocation": "CurrentUser",
        "Name": "CN=IdentityServerSigning"
      }
    },
    
  13. In Visual Studio, create an Azure App Service publish profile for the Server project. From the menu bar, select: Build > Publish > New > Azure > Azure App Service (Windows or Linux). When Visual Studio is connected to an Azure subscription, you can set the View of Azure resources by Resource type. Navigate within the Web App list to find the App Service for the app and select it. Select Finish.

  14. When Visual Studio returns to the Publish window, the key vault and SQL Server database service dependencies are automatically detected.

    No configuration changes to the default settings are required for the key vault service.

    For testing purposes, an app's local SQLite database, which is configured by default by the Blazor template, can be deployed with the app without additional configuration. Configuring a different database for Identity Server in production is beyond the scope of this article. For more information, see the database resources in the following documentation sets:

  15. Select the Edit link under the deployment profile name at the top of the window. Change the destination URL to the site's custom domain URL (for example, https://www.contoso.com). Save the settings.

  16. Publish the app. Visual Studio opens a browser window and requests the site at its custom domain.

The Azure documentation contains additional detail on using Azure services and custom domains with TLS binding in App Service, including information on using CNAME records instead of A records. For more information, see the following resources:

We recommend using a new in-private or incognito browser window for each app test run after a change to the app, app configuration, or Azure services in the Azure portal. Lingering cookies from a previous test run can result in failed authentication or authorization when testing the site even when the site's configuration is correct. For more information on how to configure Visual Studio to open a new in-private or incognito browser window for each test run, see the Cookies and site data section.

When App Service configuration is changed in the Azure portal, the updates generally take effect quickly but aren't instant. Sometimes, you must wait a short period for an App Service to restart in order for a configuration change to take effect.

If troubleshooting an Identity Server key-signing certificate loading problem, execute the following command in an Azure portal Kudu PowerShell command shell. The command provides a list of certificates that the app can access from the CurrentUser > My certificate store. The output includes certificate subjects and thumbprints useful when debugging an app:

Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList

Troubleshoot

Logging

To enable debug or trace logging for Blazor WebAssembly authentication in ASP.NET Core 7.0 or later, see ASP.NET Core Blazor logging.

Common errors

  • Misconfiguration of the app or Identity Provider (IP)

    The most common errors are caused by incorrect configuration. The following are a few examples:

    • Depending on the requirements of the scenario, a missing or incorrect Authority, Instance, Tenant ID, Tenant domain, Client ID, or Redirect URI prevents an app from authenticating clients.
    • An incorrect access token scope prevents clients from accessing server web API endpoints.
    • Incorrect or missing server API permissions prevent clients from accessing server web API endpoints.
    • Running the app at a different port than is configured in the Redirect URI of the Identity Provider's app registration.

    Configuration sections of this article's guidance show examples of the correct configuration. Carefully check each section of the article looking for app and IP misconfiguration.

    If the configuration appears correct:

    • Analyze application logs.

    • Examine the network traffic between the client app and the IP or server app with the browser's developer tools. Often, an exact error message or a message with a clue to what's causing the problem is returned to the client by the IP or server app after making a request. Developer tools guidance is found in the following articles:

    • Decode the contents of a JSON Web Token (JWT) used for authenticating a client or accessing a server web API, depending on where the problem is occurring. For more information, see Inspect the content of a JSON Web Token (JWT).

    The documentation team responds to document feedback and bugs in articles (open an issue from the This page feedback section) but is unable to provide product support. Several public support forums are available to assist with troubleshooting an app. We recommend the following:

    The preceding forums are not owned or controlled by Microsoft.

    For non-security, non-sensitive, and non-confidential reproducible framework bug reports, open an issue with the ASP.NET Core product unit. Don't open an issue with the product unit until you've thoroughly investigated the cause of a problem and can't resolve it on your own and with the help of the community on a public support forum. The product unit isn't able to troubleshoot individual apps that are broken due to simple misconfiguration or use cases involving third-party services. If a report is sensitive or confidential in nature or describes a potential security flaw in the product that attackers may exploit, see Reporting security issues and bugs (dotnet/aspnetcore GitHub repository).

  • Unauthorized client for AAD

    info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. These requirements were not met: DenyAnonymousAuthorizationRequirement: Requires an authenticated user.

    Login callback error from AAD:

    • Error: unauthorized_client
    • Description: AADB2C90058: The provided application is not configured to allow public clients.

    To resolve the error:

    1. In the Azure portal, access the app's manifest.
    2. Set the allowPublicClient attribute to null or true.

Cookies and site data

Cookies and site data can persist across app updates and interfere with testing and troubleshooting. Clear the following when making app code changes, user account changes with the provider, or provider app configuration changes:

  • User sign-in cookies
  • App cookies
  • Cached and stored site data

One approach to prevent lingering cookies and site data from interfering with testing and troubleshooting is to:

  • Configure a browser
    • Use a browser for testing that you can configure to delete all cookie and site data each time the browser is closed.
    • Make sure that the browser is closed manually or by the IDE for any change to the app, test user, or provider configuration.
  • Use a custom command to open a browser in incognito or private mode in Visual Studio:
    • Open Browse With dialog box from Visual Studio's Run button.
    • Select the Add button.
    • Provide the path to your browser in the Program field. The following executable paths are typical installation locations for Windows 10. If your browser is installed in a different location or you aren't using Windows 10, provide the path to the browser's executable.
      • Microsoft Edge: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
    • In the Arguments field, provide the command-line option that the browser uses to open in incognito or private mode. Some browsers require the URL of the app.
      • Microsoft Edge: Use -inprivate.
      • Google Chrome: Use --incognito --new-window {URL}, where the placeholder {URL} is the URL to open (for example, https://localhost:5001).
      • Mozilla Firefox: Use -private -url {URL}, where the placeholder {URL} is the URL to open (for example, https://localhost:5001).
    • Provide a name in the Friendly name field. For example, Firefox Auth Testing.
    • Select the OK button.
    • To avoid having to select the browser profile for each iteration of testing with an app, set the profile as the default with the Set as Default button.
    • Make sure that the browser is closed by the IDE for any change to the app, test user, or provider configuration.

App upgrades

A functioning app may fail immediately after upgrading either the .NET Core SDK on the development machine or changing package versions within the app. In some cases, incoherent packages may break an app when performing major upgrades. Most of these issues can be fixed by following these instructions:

  1. Clear the local system's NuGet package caches by executing dotnet nuget locals all --clear from a command shell.
  2. Delete the project's bin and obj folders.
  3. Restore and rebuild the project.
  4. Delete all of the files in the deployment folder on the server prior to redeploying the app.

Note

Use of package versions incompatible with the app's target framework isn't supported. For information on a package, use the NuGet Gallery or FuGet Package Explorer.

Run the Server app

When testing and troubleshooting a hosted Blazor WebAssembly solution, make sure that you're running the app from the Server project. For example in Visual Studio, confirm that the Server project is highlighted in Solution Explorer before you start the app with any of the following approaches:

  • Select the Run button.
  • Use Debug > Start Debugging from the menu.
  • Press F5.

Inspect the user

The ASP.NET Core framework's test assets include a Blazor WebAssembly client app with a User component that can be useful in troubleshooting. The User component can be used directly in apps or serve as the basis for further customization:

User test component in the dotnet/aspnetcore GitHub repository

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Inspect the content of a JSON Web Token (JWT)

To decode a JSON Web Token (JWT), use Microsoft's jwt.ms tool. Values in the UI never leave your browser.

Example encoded JWT (shortened for display):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1j ... bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzpD-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-yBMKV2_nXA25Q

Example JWT decoded by the tool for an app that authenticates against Azure AAD B2C:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
  "exp": 1610059429,
  "nbf": 1610055829,
  "ver": "1.0",
  "iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-226dcc9ad298/v2.0/",
  "sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
  "aud": "70bde375-fce3-4b82-984a-b247d823a03f",
  "nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
  "iat": 1610055829,
  "auth_time": 1610055822,
  "idp": "idp.com",
  "tfp": "B2C_1_signupsignin"
}.[Signature]

Additional resources

Important

Duende Software might require you to pay a license fee for production use of Duende Identity Server. For more information, see Migrate from ASP.NET Core 5.0 to 6.0.

Note

To configure a standalone or hosted Blazor WebAssembly app to use an existing, external Identity Server instance, follow the guidance in Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library.

To create a new Blazor WebAssembly project with an authentication mechanism:

  1. Create a new project.

  2. Choose the Blazor WebAssembly App template. Select Next.

  3. Provide a Project name without using dashes (see the following WARNING). Confirm that the Location is correct. Select Next.

    Warning

    Avoid using dashes (-) in the project name that break the formation of the OIDC app identifier. Logic in the Blazor WebAssembly project template uses the project name for an OIDC app identifier in the solution's configuration. Pascal case (BlazorSample) or underscores (Blazor_Sample) are acceptable alternatives. For more information, see Dashes in a hosted Blazor WebAssembly project name break OIDC security (dotnet/aspnetcore #35337).

  4. In the Additional information dialog, select Individual Accounts as the Authentication Type to store users within the app using ASP.NET Core's Identity system.

  5. Select the ASP.NET Core Hosted checkbox.

Server app configuration

The following sections describe additions to the project when authentication support is included.

Startup class

The Startup class has the following additions.

  • In Program.cs:

    • ASP.NET Core Identity:

      builder.Services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(
              Configuration.GetConnectionString("DefaultConnection")));
      
      builder.Services.AddDefaultIdentity<ApplicationUser>(options => 
              options.SignIn.RequireConfirmedAccount = true)
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • IdentityServer with an additional AddApiAuthorization helper method that sets up default ASP.NET Core conventions on top of IdentityServer:

      builder.Services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Authentication with an additional AddIdentityServerJwt helper method that configures the app to validate JWT tokens produced by IdentityServer:

      builder.Services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • In Program.cs:

    • The IdentityServer middleware exposes the OpenID Connect (OIDC) endpoints:

      app.UseIdentityServer();
      
    • The Authentication middleware is responsible for validating request credentials and setting the user on the request context:

      app.UseAuthentication();
      
    • Authorization Middleware enables authorization capabilities:

      app.UseAuthorization();
      

Azure App Service on Linux

Specify the issuer explicitly when deploying to Azure App Service on Linux. For more information, see Introduction to authentication for Single Page Apps on ASP.NET Core.

AddApiAuthorization

The AddApiAuthorization helper method configures Identity Server for ASP.NET Core scenarios. Identity Server is a powerful and extensible framework for handling app security concerns. IdentityServer exposes unnecessary complexity for the most common scenarios. Consequently, a set of conventions and configuration options is provided that we consider a good starting point. Once your authentication needs change, the full power of IdentityServer is available to customize authentication to suit an app's requirements.

AddIdentityServerJwt

The AddIdentityServerJwt helper method configures a policy scheme for the app as the default authentication handler. The policy is configured to allow Identity to handle all requests routed to any subpath in the Identity URL space /Identity. The JwtBearerHandler handles all other requests. Additionally, this method:

  • Registers an {APPLICATION NAME}API API resource with IdentityServer with a default scope of {APPLICATION NAME}API.
  • Configures the JWT Bearer Token Middleware to validate tokens issued by IdentityServer for the app.

WeatherForecastController

In the WeatherForecastController (Controllers/WeatherForecastController.cs), the [Authorize] attribute is applied to the class. The attribute indicates that the user must be authorized based on the default policy to access the resource. The default authorization policy is configured to use the default authentication scheme, which is set up by AddIdentityServerJwt. The helper method configures JwtBearerHandler as the default handler for requests to the app.

ApplicationDbContext

In the ApplicationDbContext (Data/ApplicationDbContext.cs), DbContext extends ApiAuthorizationDbContext<TUser> to include the schema for IdentityServer. ApiAuthorizationDbContext<TUser> is derived from IdentityDbContext.

To gain full control of the database schema, inherit from one of the available Identity DbContext classes and configure the context to include the Identity schema by calling builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) in the OnModelCreating method.

OidcConfigurationController

In the OidcConfigurationController (Controllers/OidcConfigurationController.cs), the client endpoint is provisioned to serve OIDC parameters.

App settings

In the app settings file (appsettings.json) at the project root, the IdentityServer section describes the list of configured clients. In the following example, there's a single client. The client name corresponds to the app name and is mapped by convention to the OAuth ClientId parameter. The profile indicates the app type being configured. The profile is used internally to drive conventions that simplify the configuration process for the server.

"IdentityServer": {
  "Clients": {
    "{APP ASSEMBLY}.Client": {
      "Profile": "IdentityServerSPA"
    }
  }
}

The placeholder {APP ASSEMBLY} is the app's assembly name (for example, BlazorSample.Client).

Client app configuration

Authentication package

When an app is created to use Individual User Accounts (Individual), the app automatically receives a package reference for the Microsoft.AspNetCore.Components.WebAssembly.Authentication package. The package provides a set of primitives that help the app authenticate users and obtain tokens to call protected APIs.

If adding authentication to an app, manually add the Microsoft.AspNetCore.Components.WebAssembly.Authentication package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

HttpClient configuration

In Program.cs, a named HttpClient ({APP ASSEMBLY}.ServerAPI) is configured to supply HttpClient instances that include access tokens when making requests to the server API:

builder.Services.AddHttpClient("{APP ASSEMBLY}.ServerAPI", 
        client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("{APP ASSEMBLY}.ServerAPI"));

The placeholder {APP ASSEMBLY} is the app's assembly name (for example, BlazorSample.Client).

Note

If you're configuring a Blazor WebAssembly app to use an existing Identity Server instance that isn't part of a hosted Blazor solution, change the HttpClient base address registration from IWebAssemblyHostEnvironment.BaseAddress (builder.HostEnvironment.BaseAddress) to the server app's API authorization endpoint URL.

API authorization support

The support for authenticating users is plugged into the service container by the extension method provided inside the Microsoft.AspNetCore.Components.WebAssembly.Authentication package. This method sets up the services required by the app to interact with the existing authorization system.

builder.Services.AddApiAuthorization();

By default, configuration for the app is loaded by convention from _configuration/{client-id}. By convention, the client ID is set to the app's assembly name. This URL can be changed to point to a separate endpoint by calling the overload with options.

Imports file

The Microsoft.AspNetCore.Components.Authorization namespace is made available throughout the app via the _Imports.razor file:

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}.Client
@using {APPLICATION ASSEMBLY}.Client.Shared

Index page

The Index page (wwwroot/index.html) page includes a script that defines the AuthenticationService in JavaScript. AuthenticationService handles the low-level details of the OIDC protocol. The app internally calls methods defined in the script to perform the authentication operations.

<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>

App component

The App component (App.razor) is similar to the App component found in Blazor Server apps:

  • The CascadingAuthenticationState component manages exposing the AuthenticationState to the rest of the app.
  • The AuthorizeRouteView component makes sure that the current user is authorized to access a given page or otherwise renders the RedirectToLogin component.
  • The RedirectToLogin component manages redirecting unauthorized users to the login page.

Due to changes in the framework across releases of ASP.NET Core, Razor markup for the App component (App.razor) isn't shown in this section. To inspect the markup of the component for a given release, use either of the following approaches:

  • Create an app provisioned for authentication from the default Blazor WebAssembly project template for the version of ASP.NET Core that you intend to use. Inspect the App component (App.razor) in the generated app.

  • Inspect the App component (App.razor) in reference source.

    Note

    Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

RedirectToLogin component

The RedirectToLogin component (Shared/RedirectToLogin.razor):

  • Manages redirecting unauthorized users to the login page.
  • Preserves the current URL that the user is attempting to access so that they can be returned to that page if authentication is successful.
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
    protected override void OnInitialized()
    {
        Navigation.NavigateTo(
            $"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
    }
}

LoginDisplay component

The LoginDisplay component (Shared/LoginDisplay.razor) is rendered in the MainLayout component (Shared/MainLayout.razor) and manages the following behaviors:

  • For authenticated users:
    • Displays the current user name.
    • Offers a link to the user profile page in ASP.NET Core Identity.
    • Offers a button to log out of the app.
  • For anonymous users:
    • Offers the option to register.
    • Offers the option to log in.

Due to changes in the framework across releases of ASP.NET Core, Razor markup for the LoginDisplay component isn't shown in this section. To inspect the markup of the component for a given release, use either of the following approaches:

  • Create an app provisioned for authentication from the default Blazor WebAssembly project template for the version of ASP.NET Core that you intend to use. Inspect the LoginDisplay component in the generated app.

  • Inspect the LoginDisplay component in reference source. The templated content for Hosted equal to true is used.

    Note

    Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Authentication component

The page produced by the Authentication component (Pages/Authentication.razor) defines the routes required for handling different authentication stages.

The RemoteAuthenticatorView component:

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

<RemoteAuthenticatorView Action="@Action" />

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

FetchData component

The FetchData component shows how to:

  • Provision an access token.
  • Use the access token to call a protected resource API in the Server app.

The @attribute [Authorize] directive indicates to the Blazor WebAssembly authorization system that the user must be authorized in order to visit this component. The presence of the attribute in the Client app doesn't prevent the API on the server from being called without proper credentials. The Server app also must use [Authorize] on the appropriate endpoints to correctly protect them.

IAccessTokenProvider.RequestAccessToken takes care of requesting an access token that can be added to the request to call the API. If the token is cached or the service is able to provision a new access token without user interaction, the token request succeeds. Otherwise, the token request fails with an AccessTokenNotAvailableException, which is caught in a try-catch statement.

In order to obtain the actual token to include in the request, the app must check that the request succeeded by calling tokenResult.TryGetToken(out var token).

If the request was successful, the token variable is populated with the access token. The AccessToken.Value property of the token exposes the literal string to include in the Authorization request header.

If the request failed because the token couldn't be provisioned without user interaction:

  • ASP.NET Core 7.0 or later: The app navigates to AccessTokenResult.InteractiveRequestUrl using the given AccessTokenResult.InteractionOptions to allow refreshing the access token.
  • ASP.NET Core 6.0 or earlier: The token result contains a redirect URL. Navigating to this URL takes the user to the login page and back to the current page after a successful authentication.
@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Run the app

Run the app from the Server project. When using Visual Studio, either:

  • Set the Startup Projects drop down list in the toolbar to the Server API app and select the Run button.
  • Select the Server project in Solution Explorer and select the Run button in the toolbar or start the app from the Debug menu.

Name and role claim with API authorization

Custom user factory

In the Client app, create a custom user factory. Identity Server sends multiple roles as a JSON array in a single role claim. A single role is sent as a string value in the claim. The factory creates an individual role claim for each of the user's roles.

CustomUserFactory.cs:

using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);

        if (user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = account.AdditionalProperties[identity.RoleClaimType];

                if (rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
                        }
                    }
                    else
                    {
                        identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
                    }
                }
            }
        }

        return user;
    }
}

In the Client app, register the factory in Program.cs:

builder.Services.AddApiAuthorization()
    .AddAccountClaimsPrincipalFactory<CustomUserFactory>();

In the Server app, call AddRoles on the Identity builder, which adds role-related services:

using Microsoft.AspNetCore.Identity;

...

services.AddDefaultIdentity<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Configure Identity Server

Use one of the following approaches:

API authorization options

In the Server app:

  • Configure Identity Server to put the name and role claims into the ID token and access token.
  • Prevent the default mapping for roles in the JWT token handler.
using System.IdentityModel.Tokens.Jwt;
using System.Linq;

...

services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Profile Service

In the Server app, create a ProfileService implementation.

ProfileService.cs:

using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using System.Threading.Tasks;

public class ProfileService : IProfileService
{
    public ProfileService()
    {
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
        context.IssuedClaims.AddRange(nameClaim);

        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
        context.IssuedClaims.AddRange(roleClaims);

        await Task.CompletedTask;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        await Task.CompletedTask;
    }
}

In the Server app, register the Profile Service in Program.cs:

using Duende.IdentityServer.Services;

...

builder.Services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Use authorization mechanisms

In the Client app, component authorization approaches are functional at this point. Any of the authorization mechanisms in components can use a role to authorize the user:

User.Identity.Name is populated in the Client app with the user's user name, which is usually their sign-in email address.

UserManager and SignInManager

Set the user identifier claim type when a Server app requires:

In Program.cs for ASP.NET Core 6.0 or later:

using System.Security.Claims;

...

builder.Services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

In Startup.ConfigureServices for versions of ASP.NET Core earlier than 6.0:

using System.Security.Claims;

...

services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

The following WeatherForecastController logs the UserName when the Get method is called:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using BlazorSample.Server.Models;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers
{
    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly UserManager<ApplicationUser> userManager;

        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", 
            "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger, 
            UserManager<ApplicationUser> userManager)
        {
            this.logger = logger;
            this.userManager = userManager;
        }

        [HttpGet]
        public async Task<IEnumerable<WeatherForecast>> Get()
        {
            var rng = new Random();

            var user = await userManager.GetUserAsync(User);

            if (user != null)
            {
                logger.LogInformation($"User.Identity.Name: {user.UserName}");
            }

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

In the preceding example:

  • The Server project's namespace is BlazorSample.Server.
  • The Shared project's namespace is BlazorSample.Shared.

Host in Azure App Service with a custom domain and certificate

The following guidance explains:

  • How to deploy a hosted Blazor WebAssembly app with Identity Server to Azure App Service with a custom domain.
  • How to create and use a TLS certificate for HTTPS protocol communication with browsers. Although the guidance focuses on using the certificate with a custom domain, the guidance is equally applicable to using a default Azure Apps domain, for example contoso.azurewebsites.net.

For this hosting scenario, do not use the same certificate for Identity Server's token signing key and the site's HTTPS secure communication with browsers:

  • Using different certificates for these two requirements is a good security practice because it isolates private keys for each purpose.
  • TLS certificates for communication with browsers is managed independently without affecting Identity Server's token signing.
  • When Azure Key Vault supplies a certificate to an App Service app for custom domain binding, Identity Server can't obtain the same certificate from Azure Key Vault for token signing. Although configuring Identity Server to use the same TLS certificate from a physical path is possible, placing security certificates into source control is a poor practice and should be avoided in most scenarios.

In the following guidance, a self-signed certificate is created in Azure Key Vault solely for Identity Server token signing. The Identity Server configuration uses the key vault certificate via the app's CurrentUser > My certificate store. Other certificates used for HTTPS traffic with custom domains are created and configured separately from the Identity Server signing certificate.

To configure an app, Azure App Service, and Azure Key Vault to host with a custom domain and HTTPS:

  1. Create an App Service plan with an plan level of Basic B1 or higher. App Service requires a Basic B1 or higher service tier to use custom domains.

  2. Create a PFX certificate for the site's secure browser communication (HTTPS protocol) with a common name of the site's fully qualified domain name (FQDN) that your organization controls (for example, www.contoso.com). Create the certificate with:

    • Key uses
      • Digital signature validation (digitalSignature)
      • Key encipherment (keyEncipherment)
    • Enhanced/extended key uses
      • Client Authentication (1.3.6.1.5.5.7.3.2)
      • Server Authentication (1.3.6.1.5.5.7.3.1)

    To create the certificate, use one of the following approaches or any other suitable tool or online service:

    Make note of the password, which is used later to import the certificate into Azure Key Vault.

    For more information on Azure Key Vault certificates, see Azure Key Vault: Certificates.

  3. Create a new Azure Key Vault or use an existing key vault in your Azure subscription.

  4. In the key vault's Certificates area, import the PFX site certificate. Record the certificate's thumbprint, which is used in the app's configuration later.

  5. In Azure Key Vault, generate a new self-signed certificate for Identity Server token signing. Give the certificate a Certificate Name and Subject. The Subject is specified as CN={COMMON NAME}, where the {COMMON NAME} placeholder is the certificate's common name. The common name can be any alphanumeric string. For example, CN=IdentityServerSigning is a valid certificate Subject. In Issuance Policy > Advanced Policy Configuration, use the default settings. Record the certificate's thumbprint, which is used in the app's configuration later.

  6. Navigate to Azure App Service in the Azure portal and create a new App Service with the following configuration:

    • Publish set to Code.
    • Runtime stack set to the app's runtime.
    • For Sku and size, confirm that the App Service tier is Basic B1 or higher. App Service requires a Basic B1 or higher service tier to use custom domains.
  7. After Azure creates the App Service, open the app's Configuration and add a new application setting specifying the certificate thumbprints recorded earlier. The app setting key is WEBSITE_LOAD_CERTIFICATES. Separate the certificate thumbprints in the app setting value with a comma, as the following example shows:

    • Key: WEBSITE_LOAD_CERTIFICATES
    • Value: 57443A552A46DB...D55E28D412B943565,29F43A772CB6AF...1D04F0C67F85FB0B1

    In the Azure portal, saving app settings is a two-step process: Save the WEBSITE_LOAD_CERTIFICATES key-value setting, then select the Save button at the top of the blade.

  8. Select the app's TLS/SSL settings. Select Private Key Certificates (.pfx). Use the Import Key Vault Certificate process. Use the process twice to import both the site's certificate for HTTPS communication and the site's self-signed Identity Server token signing certificate.

  9. Navigate to the Custom domains blade. At your domain registrar's website, use the IP address and Custom Domain Verification ID to configure the domain. A typical domain configuration includes:

    • An A Record with a Host of @ and a value of the IP address from the Azure portal.
    • A TXT Record with a Host of asuid and the value of the verification ID generated by Azure and provided by the Azure portal.

    Make sure that you save the changes at your domain registrar's website correctly. Some registrar websites require a two-step process to save domain records: One or more records are saved individually followed by updating the domain's registration with a separate button.

  10. Return to the Custom domains blade in the Azure portal. Select Add custom domain. Select the A Record option. Provide the domain and select Validate. If the domain records are correct and propagated across the Internet, the portal allows you to select the Add custom domain button.

    It can take a few days for domain registration changes to propagate across Internet domain name servers (DNS) after they're processed by your domain registrar. If domain records aren't updated within three business days, confirm the records are correctly set with the domain registrar and contact their customer support.

  11. In the Custom domains blade, the SSL STATE for the domain is marked Not Secure. Select the Add binding link. Select the site HTTPS certificate from the key vault for the custom domain binding.

  12. In Visual Studio, open the Server project's app settings file (appsettings.json or appsettings.Production.json). In the Identity Server configuration, add the following Key section. Specify the self-signed certificate Subject for the Name key. In the following example, the certificate's common name assigned in the key vault is IdentityServerSigning, which yields a Subject of CN=IdentityServerSigning:

    "IdentityServer": {
    
      ...
    
      "Key": {
        "Type": "Store",
        "StoreName": "My",
        "StoreLocation": "CurrentUser",
        "Name": "CN=IdentityServerSigning"
      }
    },
    
  13. In Visual Studio, create an Azure App Service publish profile for the Server project. From the menu bar, select: Build > Publish > New > Azure > Azure App Service (Windows or Linux). When Visual Studio is connected to an Azure subscription, you can set the View of Azure resources by Resource type. Navigate within the Web App list to find the App Service for the app and select it. Select Finish.

  14. When Visual Studio returns to the Publish window, the key vault and SQL Server database service dependencies are automatically detected.

    No configuration changes to the default settings are required for the key vault service.

    For testing purposes, an app's local SQLite database, which is configured by default by the Blazor template, can be deployed with the app without additional configuration. Configuring a different database for Identity Server in production is beyond the scope of this article. For more information, see the database resources in the following documentation sets:

  15. Select the Edit link under the deployment profile name at the top of the window. Change the destination URL to the site's custom domain URL (for example, https://www.contoso.com). Save the settings.

  16. Publish the app. Visual Studio opens a browser window and requests the site at its custom domain.

The Azure documentation contains additional detail on using Azure services and custom domains with TLS binding in App Service, including information on using CNAME records instead of A records. For more information, see the following resources:

We recommend using a new in-private or incognito browser window for each app test run after a change to the app, app configuration, or Azure services in the Azure portal. Lingering cookies from a previous test run can result in failed authentication or authorization when testing the site even when the site's configuration is correct. For more information on how to configure Visual Studio to open a new in-private or incognito browser window for each test run, see the Cookies and site data section.

When App Service configuration is changed in the Azure portal, the updates generally take effect quickly but aren't instant. Sometimes, you must wait a short period for an App Service to restart in order for a configuration change to take effect.

If troubleshooting an Identity Server key-signing certificate loading problem, execute the following command in an Azure portal Kudu PowerShell command shell. The command provides a list of certificates that the app can access from the CurrentUser > My certificate store. The output includes certificate subjects and thumbprints useful when debugging an app:

Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList

Troubleshoot

Common errors

  • Misconfiguration of the app or Identity Provider (IP)

    The most common errors are caused by incorrect configuration. The following are a few examples:

    • Depending on the requirements of the scenario, a missing or incorrect Authority, Instance, Tenant ID, Tenant domain, Client ID, or Redirect URI prevents an app from authenticating clients.
    • An incorrect access token scope prevents clients from accessing server web API endpoints.
    • Incorrect or missing server API permissions prevent clients from accessing server web API endpoints.
    • Running the app at a different port than is configured in the Redirect URI of the Identity Provider's app registration.

    Configuration sections of this article's guidance show examples of the correct configuration. Carefully check each section of the article looking for app and IP misconfiguration.

    If the configuration appears correct:

    • Analyze application logs.

    • Examine the network traffic between the client app and the IP or server app with the browser's developer tools. Often, an exact error message or a message with a clue to what's causing the problem is returned to the client by the IP or server app after making a request. Developer tools guidance is found in the following articles:

    • Decode the contents of a JSON Web Token (JWT) used for authenticating a client or accessing a server web API, depending on where the problem is occurring. For more information, see Inspect the content of a JSON Web Token (JWT).

    The documentation team responds to document feedback and bugs in articles (open an issue from the This page feedback section) but is unable to provide product support. Several public support forums are available to assist with troubleshooting an app. We recommend the following:

    The preceding forums are not owned or controlled by Microsoft.

    For non-security, non-sensitive, and non-confidential reproducible framework bug reports, open an issue with the ASP.NET Core product unit. Don't open an issue with the product unit until you've thoroughly investigated the cause of a problem and can't resolve it on your own and with the help of the community on a public support forum. The product unit isn't able to troubleshoot individual apps that are broken due to simple misconfiguration or use cases involving third-party services. If a report is sensitive or confidential in nature or describes a potential security flaw in the product that attackers may exploit, see Reporting security issues and bugs (dotnet/aspnetcore GitHub repository).

  • Unauthorized client for AAD

    info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. These requirements were not met: DenyAnonymousAuthorizationRequirement: Requires an authenticated user.

    Login callback error from AAD:

    • Error: unauthorized_client
    • Description: AADB2C90058: The provided application is not configured to allow public clients.

    To resolve the error:

    1. In the Azure portal, access the app's manifest.
    2. Set the allowPublicClient attribute to null or true.

Cookies and site data

Cookies and site data can persist across app updates and interfere with testing and troubleshooting. Clear the following when making app code changes, user account changes with the provider, or provider app configuration changes:

  • User sign-in cookies
  • App cookies
  • Cached and stored site data

One approach to prevent lingering cookies and site data from interfering with testing and troubleshooting is to:

  • Configure a browser
    • Use a browser for testing that you can configure to delete all cookie and site data each time the browser is closed.
    • Make sure that the browser is closed manually or by the IDE for any change to the app, test user, or provider configuration.
  • Use a custom command to open a browser in incognito or private mode in Visual Studio:
    • Open Browse With dialog box from Visual Studio's Run button.
    • Select the Add button.
    • Provide the path to your browser in the Program field. The following executable paths are typical installation locations for Windows 10. If your browser is installed in a different location or you aren't using Windows 10, provide the path to the browser's executable.
      • Microsoft Edge: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
    • In the Arguments field, provide the command-line option that the browser uses to open in incognito or private mode. Some browsers require the URL of the app.
      • Microsoft Edge: Use -inprivate.
      • Google Chrome: Use --incognito --new-window {URL}, where the placeholder {URL} is the URL to open (for example, https://localhost:5001).
      • Mozilla Firefox: Use -private -url {URL}, where the placeholder {URL} is the URL to open (for example, https://localhost:5001).
    • Provide a name in the Friendly name field. For example, Firefox Auth Testing.
    • Select the OK button.
    • To avoid having to select the browser profile for each iteration of testing with an app, set the profile as the default with the Set as Default button.
    • Make sure that the browser is closed by the IDE for any change to the app, test user, or provider configuration.

App upgrades

A functioning app may fail immediately after upgrading either the .NET Core SDK on the development machine or changing package versions within the app. In some cases, incoherent packages may break an app when performing major upgrades. Most of these issues can be fixed by following these instructions:

  1. Clear the local system's NuGet package caches by executing dotnet nuget locals all --clear from a command shell.
  2. Delete the project's bin and obj folders.
  3. Restore and rebuild the project.
  4. Delete all of the files in the deployment folder on the server prior to redeploying the app.

Note

Use of package versions incompatible with the app's target framework isn't supported. For information on a package, use the NuGet Gallery or FuGet Package Explorer.

Run the Server app

When testing and troubleshooting a hosted Blazor WebAssembly solution, make sure that you're running the app from the Server project. For example in Visual Studio, confirm that the Server project is highlighted in Solution Explorer before you start the app with any of the following approaches:

  • Select the Run button.
  • Use Debug > Start Debugging from the menu.
  • Press F5.

Inspect the user

The ASP.NET Core framework's test assets include a Blazor WebAssembly client app with a User component that can be useful in troubleshooting. The User component can be used directly in apps or serve as the basis for further customization:

User test component in the dotnet/aspnetcore GitHub repository

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Inspect the content of a JSON Web Token (JWT)

To decode a JSON Web Token (JWT), use Microsoft's jwt.ms tool. Values in the UI never leave your browser.

Example encoded JWT (shortened for display):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1j ... bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzpD-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-yBMKV2_nXA25Q

Example JWT decoded by the tool for an app that authenticates against Azure AAD B2C:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
  "exp": 1610059429,
  "nbf": 1610055829,
  "ver": "1.0",
  "iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-226dcc9ad298/v2.0/",
  "sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
  "aud": "70bde375-fce3-4b82-984a-b247d823a03f",
  "nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
  "iat": 1610055829,
  "auth_time": 1610055822,
  "idp": "idp.com",
  "tfp": "B2C_1_signupsignin"
}.[Signature]

Additional resources

Note

To configure a standalone or hosted Blazor WebAssembly app to use an existing, external Identity Server instance, follow the guidance in Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library.

To create a new Blazor WebAssembly project with an authentication mechanism:

  1. Create a new project.

  2. Choose the Blazor WebAssembly App template. Select Next.

  3. Provide a Project name without using dashes (see the following WARNING). Confirm that the Location is correct. Select Next.

    Warning

    Avoid using dashes (-) in the project name that break the formation of the OIDC app identifier. Logic in the Blazor WebAssembly project template uses the project name for an OIDC app identifier in the solution's configuration. Pascal case (BlazorSample) or underscores (Blazor_Sample) are acceptable alternatives. For more information, see Dashes in a hosted Blazor WebAssembly project name break OIDC security (dotnet/aspnetcore #35337).

  4. In the Additional information dialog, select Individual Accounts as the Authentication Type to store users within the app using ASP.NET Core's Identity system.

  5. Select the ASP.NET Core Hosted checkbox.

Server app configuration

The following sections describe additions to the project when authentication support is included.

Startup class

The Startup class has the following additions.

  • In Startup.ConfigureServices:

    • ASP.NET Core Identity:

      services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(
              Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDefaultIdentity<ApplicationUser>(options => 
              options.SignIn.RequireConfirmedAccount = true)
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • IdentityServer with an additional AddApiAuthorization helper method that sets up default ASP.NET Core conventions on top of IdentityServer:

      services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Authentication with an additional AddIdentityServerJwt helper method that configures the app to validate JWT tokens produced by IdentityServer:

      services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • In Startup.Configure:

    • The IdentityServer middleware exposes the OpenID Connect (OIDC) endpoints:

      app.UseIdentityServer();
      
    • The Authentication middleware is responsible for validating request credentials and setting the user on the request context:

      app.UseAuthentication();
      
    • Authorization Middleware enables authorization capabilities:

      app.UseAuthorization();
      

Azure App Service on Linux

Specify the issuer explicitly when deploying to Azure App Service on Linux. For more information, see Introduction to authentication for Single Page Apps on ASP.NET Core.

AddApiAuthorization

The AddApiAuthorization helper method configures IdentityServer for ASP.NET Core scenarios. IdentityServer is a powerful and extensible framework for handling app security concerns. IdentityServer exposes unnecessary complexity for the most common scenarios. Consequently, a set of conventions and configuration options is provided that we consider a good starting point. Once your authentication needs change, the full power of IdentityServer is available to customize authentication to suit an app's requirements.

AddIdentityServerJwt

The AddIdentityServerJwt helper method configures a policy scheme for the app as the default authentication handler. The policy is configured to allow Identity to handle all requests routed to any subpath in the Identity URL space /Identity. The JwtBearerHandler handles all other requests. Additionally, this method:

  • Registers an {APPLICATION NAME}API API resource with IdentityServer with a default scope of {APPLICATION NAME}API.
  • Configures the JWT Bearer Token Middleware to validate tokens issued by IdentityServer for the app.

WeatherForecastController

In the WeatherForecastController (Controllers/WeatherForecastController.cs), the [Authorize] attribute is applied to the class. The attribute indicates that the user must be authorized based on the default policy to access the resource. The default authorization policy is configured to use the default authentication scheme, which is set up by AddIdentityServerJwt. The helper method configures JwtBearerHandler as the default handler for requests to the app.

ApplicationDbContext

In the ApplicationDbContext (Data/ApplicationDbContext.cs), DbContext extends ApiAuthorizationDbContext<TUser> to include the schema for IdentityServer. ApiAuthorizationDbContext<TUser> is derived from IdentityDbContext.

To gain full control of the database schema, inherit from one of the available Identity DbContext classes and configure the context to include the Identity schema by calling builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) in the OnModelCreating method.

OidcConfigurationController

In the OidcConfigurationController (Controllers/OidcConfigurationController.cs), the client endpoint is provisioned to serve OIDC parameters.

App settings

In the app settings file (appsettings.json) at the project root, the IdentityServer section describes the list of configured clients. In the following example, there's a single client. The client name corresponds to the app name and is mapped by convention to the OAuth ClientId parameter. The profile indicates the app type being configured. The profile is used internally to drive conventions that simplify the configuration process for the server.

"IdentityServer": {
  "Clients": {
    "{APP ASSEMBLY}.Client": {
      "Profile": "IdentityServerSPA"
    }
  }
}

The placeholder {APP ASSEMBLY} is the app's assembly name (for example, BlazorSample.Client).

Client app configuration

Authentication package

When an app is created to use Individual User Accounts (Individual), the app automatically receives a package reference for the Microsoft.AspNetCore.Components.WebAssembly.Authentication package. The package provides a set of primitives that help the app authenticate users and obtain tokens to call protected APIs.

If adding authentication to an app, manually add the Microsoft.AspNetCore.Components.WebAssembly.Authentication package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

HttpClient configuration

In Program.cs, a named HttpClient ({APP ASSEMBLY}.ServerAPI) is configured to supply HttpClient instances that include access tokens when making requests to the server API:

builder.Services.AddHttpClient("{APP ASSEMBLY}.ServerAPI", 
        client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("{APP ASSEMBLY}.ServerAPI"));

The placeholder {APP ASSEMBLY} is the app's assembly name (for example, BlazorSample.Client).

Note

If you're configuring a Blazor WebAssembly app to use an existing Identity Server instance that isn't part of a hosted Blazor solution, change the HttpClient base address registration from IWebAssemblyHostEnvironment.BaseAddress (builder.HostEnvironment.BaseAddress) to the server app's API authorization endpoint URL.

API authorization support

The support for authenticating users is plugged into the service container by the extension method provided inside the Microsoft.AspNetCore.Components.WebAssembly.Authentication package. This method sets up the services required by the app to interact with the existing authorization system.

builder.Services.AddApiAuthorization();

By default, configuration for the app is loaded by convention from _configuration/{client-id}. By convention, the client ID is set to the app's assembly name. This URL can be changed to point to a separate endpoint by calling the overload with options.

Imports file

The Microsoft.AspNetCore.Components.Authorization namespace is made available throughout the app via the _Imports.razor file:

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}.Client
@using {APPLICATION ASSEMBLY}.Client.Shared

Index page

The Index page (wwwroot/index.html) page includes a script that defines the AuthenticationService in JavaScript. AuthenticationService handles the low-level details of the OIDC protocol. The app internally calls methods defined in the script to perform the authentication operations.

<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>

App component

The App component (App.razor) is similar to the App component found in Blazor Server apps:

  • The CascadingAuthenticationState component manages exposing the AuthenticationState to the rest of the app.
  • The AuthorizeRouteView component makes sure that the current user is authorized to access a given page or otherwise renders the RedirectToLogin component.
  • The RedirectToLogin component manages redirecting unauthorized users to the login page.

Due to changes in the framework across releases of ASP.NET Core, Razor markup for the App component (App.razor) isn't shown in this section. To inspect the markup of the component for a given release, use either of the following approaches:

  • Create an app provisioned for authentication from the default Blazor WebAssembly project template for the version of ASP.NET Core that you intend to use. Inspect the App component (App.razor) in the generated app.

  • Inspect the App component (App.razor) in reference source.

    Note

    Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

RedirectToLogin component

The RedirectToLogin component (Shared/RedirectToLogin.razor):

  • Manages redirecting unauthorized users to the login page.
  • Preserves the current URL that the user is attempting to access so that they can be returned to that page if authentication is successful.
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
    protected override void OnInitialized()
    {
        Navigation.NavigateTo(
            $"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
    }
}

LoginDisplay component

The LoginDisplay component (Shared/LoginDisplay.razor) is rendered in the MainLayout component (Shared/MainLayout.razor) and manages the following behaviors:

  • For authenticated users:
    • Displays the current user name.
    • Offers a link to the user profile page in ASP.NET Core Identity.
    • Offers a button to log out of the app.
  • For anonymous users:
    • Offers the option to register.
    • Offers the option to log in.

Due to changes in the framework across releases of ASP.NET Core, Razor markup for the LoginDisplay component isn't shown in this section. To inspect the markup of the component for a given release, use either of the following approaches:

  • Create an app provisioned for authentication from the default Blazor WebAssembly project template for the version of ASP.NET Core that you intend to use. Inspect the LoginDisplay component in the generated app.

  • Inspect the LoginDisplay component in reference source. The templated content for Hosted equal to true is used.

    Note

    Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Authentication component

The page produced by the Authentication component (Pages/Authentication.razor) defines the routes required for handling different authentication stages.

The RemoteAuthenticatorView component:

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

<RemoteAuthenticatorView Action="@Action" />

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

FetchData component

The FetchData component shows how to:

  • Provision an access token.
  • Use the access token to call a protected resource API in the Server app.

The @attribute [Authorize] directive indicates to the Blazor WebAssembly authorization system that the user must be authorized in order to visit this component. The presence of the attribute in the Client app doesn't prevent the API on the server from being called without proper credentials. The Server app also must use [Authorize] on the appropriate endpoints to correctly protect them.

IAccessTokenProvider.RequestAccessToken takes care of requesting an access token that can be added to the request to call the API. If the token is cached or the service is able to provision a new access token without user interaction, the token request succeeds. Otherwise, the token request fails with an AccessTokenNotAvailableException, which is caught in a try-catch statement.

In order to obtain the actual token to include in the request, the app must check that the request succeeded by calling tokenResult.TryGetToken(out var token).

If the request was successful, the token variable is populated with the access token. The AccessToken.Value property of the token exposes the literal string to include in the Authorization request header.

If the request failed because the token couldn't be provisioned without user interaction:

  • ASP.NET Core 7.0 or later: The app navigates to AccessTokenResult.InteractiveRequestUrl using the given AccessTokenResult.InteractionOptions to allow refreshing the access token.
  • ASP.NET Core 6.0 or earlier: The token result contains a redirect URL. Navigating to this URL takes the user to the login page and back to the current page after a successful authentication.
@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Run the app

Run the app from the Server project. When using Visual Studio, either:

  • Set the Startup Projects drop down list in the toolbar to the Server API app and select the Run button.
  • Select the Server project in Solution Explorer and select the Run button in the toolbar or start the app from the Debug menu.

Name and role claim with API authorization

Custom user factory

In the Client app, create a custom user factory. Identity Server sends multiple roles as a JSON array in a single role claim. A single role is sent as a string value in the claim. The factory creates an individual role claim for each of the user's roles.

CustomUserFactory.cs:

using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);

        if (user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = account.AdditionalProperties[identity.RoleClaimType];

                if (rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
                        }
                    }
                    else
                    {
                        identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
                    }
                }
            }
        }

        return user;
    }
}

In the Client app, register the factory in Program.cs:

builder.Services.AddApiAuthorization()
    .AddAccountClaimsPrincipalFactory<CustomUserFactory>();

In the Server app, call AddRoles on the Identity builder, which adds role-related services:

using Microsoft.AspNetCore.Identity;

...

services.AddDefaultIdentity<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Configure Identity Server

Use one of the following approaches:

API authorization options

In the Server app:

  • Configure Identity Server to put the name and role claims into the ID token and access token.
  • Prevent the default mapping for roles in the JWT token handler.
using System.IdentityModel.Tokens.Jwt;
using System.Linq;

...

services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Profile Service

In the Server app, create a ProfileService implementation.

ProfileService.cs:

using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Services;
using System.Threading.Tasks;

public class ProfileService : IProfileService
{
    public ProfileService()
    {
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
        context.IssuedClaims.AddRange(nameClaim);

        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
        context.IssuedClaims.AddRange(roleClaims);

        await Task.CompletedTask;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        await Task.CompletedTask;
    }
}

In the Server app, register the Profile Service in Startup.ConfigureServices:

using IdentityServer4.Services;

...

services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Use authorization mechanisms

In the Client app, component authorization approaches are functional at this point. Any of the authorization mechanisms in components can use a role to authorize the user:

User.Identity.Name is populated in the Client app with the user's user name, which is usually their sign-in email address.

UserManager and SignInManager

Set the user identifier claim type when a Server app requires:

In Program.cs for ASP.NET Core 6.0 or later:

using System.Security.Claims;

...

builder.Services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

In Startup.ConfigureServices for versions of ASP.NET Core earlier than 6.0:

using System.Security.Claims;

...

services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

The following WeatherForecastController logs the UserName when the Get method is called:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using BlazorSample.Server.Models;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers
{
    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly UserManager<ApplicationUser> userManager;

        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", 
            "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger, 
            UserManager<ApplicationUser> userManager)
        {
            this.logger = logger;
            this.userManager = userManager;
        }

        [HttpGet]
        public async Task<IEnumerable<WeatherForecast>> Get()
        {
            var rng = new Random();

            var user = await userManager.GetUserAsync(User);

            if (user != null)
            {
                logger.LogInformation($"User.Identity.Name: {user.UserName}");
            }

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

In the preceding example:

  • The Server project's namespace is BlazorSample.Server.
  • The Shared project's namespace is BlazorSample.Shared.

Token signing scenarios with Azure App Service

Two scenarios are covered. Follow the guidance in either one of the following sections:

Host in Azure App Service with automatic token signing and Data Protection key security using Azure Key Vault

The guidance in this section explains:

  • How to deploy a hosted Blazor WebAssembly app with Identity Server to Azure App Service.
  • How to use automatic Identity Server token signing with Data Protection keys secured by Azure Key Vault.

Note

To create and deploy a TLS certificate for HTTPS protocol communication after following the guidance in this section, see the Use a custom domain and TLS certificate for HTTPS protocol communication in Azure App Service section of this article.

  1. Create an App Service plan.

    Note

    An App Service plan with an plan level of Basic B1 or higher tier is required if you also plan to use one or more custom domains with the app.

  2. Create a new Azure Key Vault or use an existing key vault in your Azure subscription.

  3. Navigate to Azure App Service in the Azure portal and create a new App Service with the following configuration:

    • Publish set to Code.
    • Runtime stack set to the app's runtime.
    • For Sku and size, confirm that the App Service tier is Basic B1 or higher if you also plan to use a custom domain. App Service requires a Basic B1 or higher service tier to use custom domains.
  4. Configure the app to use automatic token signing and Azure Key Vault to store and protect ASP.NET Core Data Protection keys:

    Identity Server token signing is automatic by default. Identity Server also uses ASP.NET Core Data Protection by default. No configuration in the app is required for these features. For more information, see Automatic key management (Duende Software documentation).

    The app requires configuration to use Azure Key Vault to store and protect the keys used for ASP.NET Core Data Protection. Use the guidance in the following ASP.NET Core documentation resources to configure the required features:

  5. In Visual Studio, create an Azure App Service publish profile for the Server project. From the menu bar, select: Build > Publish > New > Azure > Azure App Service (Windows or Linux). When Visual Studio is connected to an Azure subscription, you can set the View of Azure resources by Resource type. Navigate within the Web App list to find the App Service for the app and select it. Select Finish.

  6. When Visual Studio returns to the Publish window, the key vault and SQL Server database service dependencies are automatically detected.

    No configuration changes to the default settings are required for the key vault service.

    For testing purposes, an app's local SQLite database, which is configured by default by the Blazor template, can be deployed with the app without additional configuration. Configuring a different database for Identity Server in production is beyond the scope of this article. For more information, see the database resources in the following documentation sets:

  7. Publish the app. Visual Studio opens a browser window and requests the site.

The Azure documentation contains additional detail on using Azure services. See the following resources:

We recommend using a new in-private or incognito browser window for each app test run after a change to the app, app configuration, or Azure services in the Azure portal. Lingering cookies from a previous test run can result in failed authentication or authorization when testing the site even when the site's configuration is correct. For more information on how to configure Visual Studio to open a new in-private or incognito browser window for each test run, see the Cookies and site data section.

When App Service configuration is changed in the Azure portal, the updates generally take effect quickly but aren't instant. Sometimes, you must wait a short period for an App Service to restart in order for a configuration change to take effect.

Host in Azure App Service with certificate token signing

The guidance in this section explains:

  • How to deploy a hosted Blazor WebAssembly app with Identity Server to Azure App Service.
  • How to create and use a TLS certificate for Identity Server token signing.

Note

To create and deploy a TLS certificate for HTTPS protocol communication after following the guidance in this section, see the Use a custom domain and TLS certificate for HTTPS protocol communication in Azure App Service section of this article.

In the following guidance, a self-signed certificate is created in Azure Key Vault solely for Identity Server token signing. The Identity Server configuration uses the key vault certificate following different approaches for Windows App Service and Linux App Service:

  • Windows App Service: The app's CurrentUser > My certificate store is accessed by the app to load the certificate.
  • Linux App Service: The app loads the certificate manually.

Warning

When seeking to use a certificate for Identity Server token signing and a custom domain, do not use the same certificate for Identity Server's token signing and the site's HTTPS secure communication with browsers:

  • Using different certificates for these two requirements is a good security practice because it isolates private keys for each purpose.
  • TLS certificates for communication with browsers is managed independently without affecting Identity Server's token signing.
  • When Azure Key Vault supplies a certificate to an App Service app for custom domain binding, Identity Server can't obtain the same certificate from Azure Key Vault for token signing. Although configuring Identity Server to use the same TLS certificate from a physical path is possible, placing security certificates into source control is a poor practice and should be avoided in most scenarios.

To configure an app, Azure App Service, and Azure Key Vault to host with a custom token signing certificate:

  1. Create an App Service plan.

    Note

    An App Service plan with an plan level of Basic B1 or higher tier is required if you also plan to use one or more custom domains with the app.

  2. Create a new Azure Key Vault or use an existing key vault in your Azure subscription.

  3. In Azure Key Vault, generate a new self-signed certificate for Identity Server token signing. Give the certificate a Certificate Name and Subject. The Subject is specified as CN={COMMON NAME}, where the {COMMON NAME} placeholder is the certificate's common name. The common name can be any alphanumeric string. For example, CN=IdentityServerSigning is a valid certificate Subject. In Issuance Policy > Advanced Policy Configuration, use the default settings.

    To create the certificate, use one of the following approaches or any other suitable tool or online service:

    Record the certificate's thumbprint, which is used in the app's configuration later.

    For more information on Azure Key Vault certificates, see Azure Key Vault: Certificates.

  4. Navigate to Azure App Service in the Azure portal and create a new App Service with the following configuration:

    • Publish set to Code.
    • Runtime stack set to the app's runtime.
    • For Sku and size, confirm that the App Service tier is Basic B1 or higher. App Service requires a Basic B1 or higher service tier to use custom domains.
  5. After Azure creates the App Service, open the app's Configuration and add a new application setting specifying the certificate thumbprint recorded earlier. The app setting key is WEBSITE_LOAD_CERTIFICATES:

    • Key: WEBSITE_LOAD_CERTIFICATES
    • Value: 57443A552A46DB...D55E28D412B943565

    In the Azure portal, saving app settings is a two-step process: Save the WEBSITE_LOAD_CERTIFICATES key-value setting, then select the Save button at the top of the blade.

  6. Select the app's TLS/SSL settings. Select Private Key Certificates (.pfx). Use the Import Key Vault Certificate process to import the site's self-signed Identity Server token signing certificate.

  7. Configure the app to use the token signing certificate based on your choice of host OS, either Windows App Service or Linux App Service:

    Windows App Service

    In Visual Studio, open the Server project's app settings file (appsettings.json or appsettings.Production.json). In the Identity Server configuration, add the following Key section. Specify the self-signed certificate Subject for the Name key. In the following example, the certificate's common name assigned in the key vault is IdentityServerSigning, which yields a Subject of CN=IdentityServerSigning:

    "IdentityServer": {
    
      ...
    
      "Key": {
        "Type": "Store",
        "StoreName": "My",
        "StoreLocation": "CurrentUser",
        "Name": "CN=IdentityServerSigning"
      }
    },
    

    If troubleshooting an Identity Server key-signing certificate loading problem, execute the following command in an Azure portal Kudu PowerShell command shell. The command provides a list of certificates that the app can access from the CurrentUser > My certificate store. The output includes certificate subjects and thumbprints useful when debugging an app:

    Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList
    

    Linux App Service

    Linux can't use a Windows certificate store to load a TLS certificate. On Linux App Service, certificates are placed at the path /var/ssl/private/. Manually load and use the certificate from the preceding path.

    If the following namespaces aren't already present at the top of Startup.cs in the Server project, add them to the file:

    using System;
    using System.IO;
    using System.Security.Cryptography.X509Certificates;
    

    In Startup.ConfigureServices, update the Identity Server configuration to use a manually-loaded certificate. In the following example, the {THUMBPRINT} placeholder is the certificate's thumbprint:

    var certPath = "/var/ssl/private/{THUMBPRINT}.p12";
    
    if (File.Exists(certPath))
    {
        var bytes = File.ReadAllBytes(certPath);
        var certificate = new X509Certificate2(bytes);
    
        services.AddIdentityServer()
            .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => { })
            .AddSigningCredential(certificate);
    }
    else
    {
        throw new FileNotFoundException($"Certificate path: {certPath}.");
    }
    
  8. In Visual Studio, create an Azure App Service publish profile for the Server project. From the menu bar, select: Build > Publish > New > Azure > Azure App Service (Windows or Linux). When Visual Studio is connected to an Azure subscription, you can set the View of Azure resources by Resource type. Navigate within the Web App list to find the App Service for the app and select it. Select Finish.

  9. When Visual Studio returns to the Publish window, the key vault and SQL Server database service dependencies are automatically detected.

    No configuration changes to the default settings are required for the key vault service.

    For testing purposes, an app's local SQLite database, which is configured by default by the Blazor template, can be deployed with the app without additional configuration. Configuring a different database for Identity Server in production is beyond the scope of this article. For more information, see the database resources in the following documentation sets:

  10. Publish the app. Visual Studio opens a browser window and requests the site.

The Azure documentation contains additional detail on using Azure services. See the following resources:

We recommend using a new in-private or incognito browser window for each app test run after a change to the app, app configuration, or Azure services in the Azure portal. Lingering cookies from a previous test run can result in failed authentication or authorization when testing the site even when the site's configuration is correct. For more information on how to configure Visual Studio to open a new in-private or incognito browser window for each test run, see the Cookies and site data section.

When App Service configuration is changed in the Azure portal, the updates generally take effect quickly but aren't instant. Sometimes, you must wait a short period for an App Service to restart in order for a configuration change to take effect.

Use a custom domain and TLS certificate for HTTPS protocol communication in Azure App Service

The guidance in this section explains how to create and use a TLS certificate for HTTPS protocol communication with browsers for an app that has been deployed to Azure App Service. Although the guidance focuses on using the certificate with a custom domain, the guidance is equally applicable to using a default Azure Apps domain, for example, contoso.azurewebsites.net.

Note

The guidance in this section assumes that the app is already hosted in Azure App Service. If deploying to App Service for the first time, follow the guidance in the Token signing scenarios with Azure App Service section before using the guidance in this section.

To configure an app, Azure App Service, and Azure Key Vault to host with a custom domain and HTTPS:

  1. Use an App Service plan with an plan level of Basic B1 or higher. App Service requires a Basic B1 or higher service tier to use custom domains.

  2. Create a PFX certificate for the site's secure browser communication (HTTPS protocol) with a common name of the site's fully qualified domain name (FQDN) that your organization controls (for example, www.contoso.com). Create the certificate with:

    • Key uses
      • Digital signature validation (digitalSignature)
      • Key encipherment (keyEncipherment)
    • Enhanced/extended key uses
      • Client Authentication (1.3.6.1.5.5.7.3.2)
      • Server Authentication (1.3.6.1.5.5.7.3.1)

    To create the certificate, use one of the following approaches or any other suitable tool or online service:

    Make note of the password, which is used later to import the certificate into Azure Key Vault.

    For more information on Azure Key Vault certificates, see Azure Key Vault: Certificates.

  3. Create a new Azure Key Vault or use an existing key vault in your Azure subscription.

  4. In the key vault's Certificates area, import the PFX site certificate. Record the certificate's thumbprint, which is used in the app's configuration later.

  5. In Azure App Service in the Azure portal for Sku and size, confirm that the App Service tier is Basic B1 or higher. App Service requires a Basic B1 or higher service tier to use custom domains.

  6. Open the app's Configuration in App Service and add a new application setting with a key of WEBSITE_LOAD_CERTIFICATES (if it doesn't already exist) or modify the existing WEBSITE_LOAD_CERTIFICATES application setting. Specify the certificate thumbprint of the TLS certificate recorded earlier:

    • When using automatic token signing, there is only a single thumbprint for the HTTPS communication. Example key: WEBSITE_LOAD_CERTIFICATES Example value: 57443A552A46DB...D55E28D412B943565
    • When using a separate token signing certificate, there are two thumbprints for the setting with thumbprint values separated by a comma. Example key: WEBSITE_LOAD_CERTIFICATES Example value: 57443A552A46DB...D55E28D412B943565,29F43A772CB6AF...1D04F0C67F85FB0B1

    In the Azure portal, saving app settings is a two-step process: Save the WEBSITE_LOAD_CERTIFICATES key-value setting, then select the Save button at the top of the blade.

  7. Select the app's TLS/SSL settings. Select Private Key Certificates (.pfx). Use the Import Key Vault Certificate process to import the site's certificate for HTTPS communication. If two certificates are used by the app, one for using a custom domain and one for Identity Server key signing, both certificates must be imported from Azure Key Vault using this process.

  8. Navigate to the Custom domains blade. At your domain registrar's website, use the IP address and Custom Domain Verification ID to configure the domain. A typical domain configuration includes:

    • An A Record with a Host of @ and a value of the IP address from the Azure portal.
    • A TXT Record with a Host of asuid and the value of the verification ID generated by Azure and provided by the Azure portal.

    Make sure that you save the changes at your domain registrar's website correctly. Some registrar websites require a two-step process to save domain records: One or more records are saved individually followed by updating the domain's registration with a separate button.

  9. Return to the Custom domains blade in the Azure portal. Select Add custom domain. Select the A Record option. Provide the domain and select Validate. If the domain records are correct and propagated across the Internet, the portal allows you to select the Add custom domain button.

    It can take a few days for domain registration changes to propagate across Internet domain name servers (DNS) after they're processed by your domain registrar. If domain records aren't updated within three business days, confirm the records are correctly set with the domain registrar and contact their customer support.

  10. In the Custom domains blade, the SSL STATE for the domain is marked Not Secure. Select the Add binding link. Select the site HTTPS certificate from the key vault for the custom domain binding.

The Azure documentation contains additional detail on using Azure services and custom domains with TLS binding in App Service, including information on using CNAME records instead of A records. For more information, see the following resources:

We recommend using a new in-private or incognito browser window for each app test run after a change to the app, app configuration, or Azure services in the Azure portal. Lingering cookies from a previous test run can result in failed authentication or authorization when testing the site even when the site's configuration is correct. For more information on how to configure Visual Studio to open a new in-private or incognito browser window for each test run, see the Cookies and site data section.

When App Service configuration is changed in the Azure portal, the updates generally take effect quickly but aren't instant. Sometimes, you must wait a short period for an App Service to restart in order for a configuration change to take effect.

If troubleshooting an Identity Server key-signing certificate loading problem, execute the following command in an Azure portal Kudu PowerShell command shell. The command provides a list of certificates that the app can access from the CurrentUser > My certificate store. The output includes certificate subjects and thumbprints useful when debugging an app:

Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList

Troubleshoot

Common errors

  • Misconfiguration of the app or Identity Provider (IP)

    The most common errors are caused by incorrect configuration. The following are a few examples:

    • Depending on the requirements of the scenario, a missing or incorrect Authority, Instance, Tenant ID, Tenant domain, Client ID, or Redirect URI prevents an app from authenticating clients.
    • An incorrect access token scope prevents clients from accessing server web API endpoints.
    • Incorrect or missing server API permissions prevent clients from accessing server web API endpoints.
    • Running the app at a different port than is configured in the Redirect URI of the Identity Provider's app registration.

    Configuration sections of this article's guidance show examples of the correct configuration. Carefully check each section of the article looking for app and IP misconfiguration.

    If the configuration appears correct:

    • Analyze application logs.

    • Examine the network traffic between the client app and the IP or server app with the browser's developer tools. Often, an exact error message or a message with a clue to what's causing the problem is returned to the client by the IP or server app after making a request. Developer tools guidance is found in the following articles:

    • Decode the contents of a JSON Web Token (JWT) used for authenticating a client or accessing a server web API, depending on where the problem is occurring. For more information, see Inspect the content of a JSON Web Token (JWT).

    The documentation team responds to document feedback and bugs in articles (open an issue from the This page feedback section) but is unable to provide product support. Several public support forums are available to assist with troubleshooting an app. We recommend the following:

    The preceding forums are not owned or controlled by Microsoft.

    For non-security, non-sensitive, and non-confidential reproducible framework bug reports, open an issue with the ASP.NET Core product unit. Don't open an issue with the product unit until you've thoroughly investigated the cause of a problem and can't resolve it on your own and with the help of the community on a public support forum. The product unit isn't able to troubleshoot individual apps that are broken due to simple misconfiguration or use cases involving third-party services. If a report is sensitive or confidential in nature or describes a potential security flaw in the product that attackers may exploit, see Reporting security issues and bugs (dotnet/aspnetcore GitHub repository).

  • Unauthorized client for AAD

    info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. These requirements were not met: DenyAnonymousAuthorizationRequirement: Requires an authenticated user.

    Login callback error from AAD:

    • Error: unauthorized_client
    • Description: AADB2C90058: The provided application is not configured to allow public clients.

    To resolve the error:

    1. In the Azure portal, access the app's manifest.
    2. Set the allowPublicClient attribute to null or true.

Cookies and site data

Cookies and site data can persist across app updates and interfere with testing and troubleshooting. Clear the following when making app code changes, user account changes with the provider, or provider app configuration changes:

  • User sign-in cookies
  • App cookies
  • Cached and stored site data

One approach to prevent lingering cookies and site data from interfering with testing and troubleshooting is to:

  • Configure a browser
    • Use a browser for testing that you can configure to delete all cookie and site data each time the browser is closed.
    • Make sure that the browser is closed manually or by the IDE for any change to the app, test user, or provider configuration.
  • Use a custom command to open a browser in incognito or private mode in Visual Studio:
    • Open Browse With dialog box from Visual Studio's Run button.
    • Select the Add button.
    • Provide the path to your browser in the Program field. The following executable paths are typical installation locations for Windows 10. If your browser is installed in a different location or you aren't using Windows 10, provide the path to the browser's executable.
      • Microsoft Edge: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
    • In the Arguments field, provide the command-line option that the browser uses to open in incognito or private mode. Some browsers require the URL of the app.
      • Microsoft Edge: Use -inprivate.
      • Google Chrome: Use --incognito --new-window {URL}, where the placeholder {URL} is the URL to open (for example, https://localhost:5001).
      • Mozilla Firefox: Use -private -url {URL}, where the placeholder {URL} is the URL to open (for example, https://localhost:5001).
    • Provide a name in the Friendly name field. For example, Firefox Auth Testing.
    • Select the OK button.
    • To avoid having to select the browser profile for each iteration of testing with an app, set the profile as the default with the Set as Default button.
    • Make sure that the browser is closed by the IDE for any change to the app, test user, or provider configuration.

App upgrades

A functioning app may fail immediately after upgrading either the .NET Core SDK on the development machine or changing package versions within the app. In some cases, incoherent packages may break an app when performing major upgrades. Most of these issues can be fixed by following these instructions:

  1. Clear the local system's NuGet package caches by executing dotnet nuget locals all --clear from a command shell.
  2. Delete the project's bin and obj folders.
  3. Restore and rebuild the project.
  4. Delete all of the files in the deployment folder on the server prior to redeploying the app.

Note

Use of package versions incompatible with the app's target framework isn't supported. For information on a package, use the NuGet Gallery or FuGet Package Explorer.

Run the Server app

When testing and troubleshooting a hosted Blazor WebAssembly solution, make sure that you're running the app from the Server project. For example in Visual Studio, confirm that the Server project is highlighted in Solution Explorer before you start the app with any of the following approaches:

  • Select the Run button.
  • Use Debug > Start Debugging from the menu.
  • Press F5.

Inspect the user

The ASP.NET Core framework's test assets include a Blazor WebAssembly client app with a User component that can be useful in troubleshooting. The User component can be used directly in apps or serve as the basis for further customization:

User test component in the dotnet/aspnetcore GitHub repository

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Inspect the content of a JSON Web Token (JWT)

To decode a JSON Web Token (JWT), use Microsoft's jwt.ms tool. Values in the UI never leave your browser.

Example encoded JWT (shortened for display):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1j ... bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzpD-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-yBMKV2_nXA25Q

Example JWT decoded by the tool for an app that authenticates against Azure AAD B2C:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
  "exp": 1610059429,
  "nbf": 1610055829,
  "ver": "1.0",
  "iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-226dcc9ad298/v2.0/",
  "sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
  "aud": "70bde375-fce3-4b82-984a-b247d823a03f",
  "nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
  "iat": 1610055829,
  "auth_time": 1610055822,
  "idp": "idp.com",
  "tfp": "B2C_1_signupsignin"
}.[Signature]

Additional resources

Note

To configure a standalone or hosted Blazor WebAssembly app to use an existing, external Identity Server instance, follow the guidance in Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library.

To create a new Blazor WebAssembly project with an authentication mechanism:

  1. Create a new project.

  2. Choose the Blazor WebAssembly App template. Select Next.

  3. Provide a Project name without using dashes (see the following WARNING). Confirm that the Location is correct. Select Next.

    Warning

    Avoid using dashes (-) in the project name that break the formation of the OIDC app identifier. Logic in the Blazor WebAssembly project template uses the project name for an OIDC app identifier in the solution's configuration. Pascal case (BlazorSample) or underscores (Blazor_Sample) are acceptable alternatives. For more information, see Dashes in a hosted Blazor WebAssembly project name break OIDC security (dotnet/aspnetcore #35337).

  4. In the Additional information dialog, select Individual Accounts as the Authentication Type to store users within the app using ASP.NET Core's Identity system.

  5. Select the ASP.NET Core Hosted checkbox.

Server app configuration

The following sections describe additions to the project when authentication support is included.

Startup class

The Startup class has the following additions.

  • In Startup.ConfigureServices:

    • ASP.NET Core Identity:

      services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(
              Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDefaultIdentity<ApplicationUser>(options => 
              options.SignIn.RequireConfirmedAccount = true)
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • IdentityServer with an additional AddApiAuthorization helper method that sets up default ASP.NET Core conventions on top of IdentityServer:

      services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Authentication with an additional AddIdentityServerJwt helper method that configures the app to validate JWT tokens produced by IdentityServer:

      services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • In Startup.Configure:

    • The IdentityServer middleware exposes the OpenID Connect (OIDC) endpoints:

      app.UseIdentityServer();
      
    • The Authentication middleware is responsible for validating request credentials and setting the user on the request context:

      app.UseAuthentication();
      
    • Authorization Middleware enables authorization capabilities:

      app.UseAuthorization();
      

Azure App Service on Linux

Specify the issuer explicitly when deploying to Azure App Service on Linux. For more information, see Introduction to authentication for Single Page Apps on ASP.NET Core.

AddApiAuthorization

The AddApiAuthorization helper method configures IdentityServer for ASP.NET Core scenarios. IdentityServer is a powerful and extensible framework for handling app security concerns. IdentityServer exposes unnecessary complexity for the most common scenarios. Consequently, a set of conventions and configuration options is provided that we consider a good starting point. Once your authentication needs change, the full power of IdentityServer is available to customize authentication to suit an app's requirements.

AddIdentityServerJwt

The AddIdentityServerJwt helper method configures a policy scheme for the app as the default authentication handler. The policy is configured to allow Identity to handle all requests routed to any subpath in the Identity URL space /Identity. The JwtBearerHandler handles all other requests. Additionally, this method:

  • Registers an {APPLICATION NAME}API API resource with IdentityServer with a default scope of {APPLICATION NAME}API.
  • Configures the JWT Bearer Token Middleware to validate tokens issued by IdentityServer for the app.

WeatherForecastController

In the WeatherForecastController (Controllers/WeatherForecastController.cs), the [Authorize] attribute is applied to the class. The attribute indicates that the user must be authorized based on the default policy to access the resource. The default authorization policy is configured to use the default authentication scheme, which is set up by AddIdentityServerJwt. The helper method configures JwtBearerHandler as the default handler for requests to the app.

ApplicationDbContext

In the ApplicationDbContext (Data/ApplicationDbContext.cs), DbContext extends ApiAuthorizationDbContext<TUser> to include the schema for IdentityServer. ApiAuthorizationDbContext<TUser> is derived from IdentityDbContext.

To gain full control of the database schema, inherit from one of the available Identity DbContext classes and configure the context to include the Identity schema by calling builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) in the OnModelCreating method.

OidcConfigurationController

In the OidcConfigurationController (Controllers/OidcConfigurationController.cs), the client endpoint is provisioned to serve OIDC parameters.

App settings

In the app settings file (appsettings.json) at the project root, the IdentityServer section describes the list of configured clients. In the following example, there's a single client. The client name corresponds to the app name and is mapped by convention to the OAuth ClientId parameter. The profile indicates the app type being configured. The profile is used internally to drive conventions that simplify the configuration process for the server.

"IdentityServer": {
  "Clients": {
    "{APP ASSEMBLY}.Client": {
      "Profile": "IdentityServerSPA"
    }
  }
}

The placeholder {APP ASSEMBLY} is the app's assembly name (for example, BlazorSample.Client).

Client app configuration

Authentication package

When an app is created to use Individual User Accounts (Individual), the app automatically receives a package reference for the Microsoft.AspNetCore.Components.WebAssembly.Authentication package. The package provides a set of primitives that help the app authenticate users and obtain tokens to call protected APIs.

If adding authentication to an app, manually add the Microsoft.AspNetCore.Components.WebAssembly.Authentication package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

HttpClient configuration

In Program.cs, a named HttpClient ({APP ASSEMBLY}.ServerAPI) is configured to supply HttpClient instances that include access tokens when making requests to the server API:

builder.Services.AddHttpClient("{APP ASSEMBLY}.ServerAPI", 
        client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("{APP ASSEMBLY}.ServerAPI"));

The placeholder {APP ASSEMBLY} is the app's assembly name (for example, BlazorSample.Client).

Note

If you're configuring a Blazor WebAssembly app to use an existing Identity Server instance that isn't part of a hosted Blazor solution, change the HttpClient base address registration from IWebAssemblyHostEnvironment.BaseAddress (builder.HostEnvironment.BaseAddress) to the server app's API authorization endpoint URL.

API authorization support

The support for authenticating users is plugged into the service container by the extension method provided inside the Microsoft.AspNetCore.Components.WebAssembly.Authentication package. This method sets up the services required by the app to interact with the existing authorization system.

builder.Services.AddApiAuthorization();

By default, configuration for the app is loaded by convention from _configuration/{client-id}. By convention, the client ID is set to the app's assembly name. This URL can be changed to point to a separate endpoint by calling the overload with options.

Imports file

The Microsoft.AspNetCore.Components.Authorization namespace is made available throughout the app via the _Imports.razor file:

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}.Client
@using {APPLICATION ASSEMBLY}.Client.Shared

Index page

The Index page (wwwroot/index.html) page includes a script that defines the AuthenticationService in JavaScript. AuthenticationService handles the low-level details of the OIDC protocol. The app internally calls methods defined in the script to perform the authentication operations.

<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>

App component

The App component (App.razor) is similar to the App component found in Blazor Server apps:

  • The CascadingAuthenticationState component manages exposing the AuthenticationState to the rest of the app.
  • The AuthorizeRouteView component makes sure that the current user is authorized to access a given page or otherwise renders the RedirectToLogin component.
  • The RedirectToLogin component manages redirecting unauthorized users to the login page.

Due to changes in the framework across releases of ASP.NET Core, Razor markup for the App component (App.razor) isn't shown in this section. To inspect the markup of the component for a given release, use either of the following approaches:

  • Create an app provisioned for authentication from the default Blazor WebAssembly project template for the version of ASP.NET Core that you intend to use. Inspect the App component (App.razor) in the generated app.

  • Inspect the App component (App.razor) in reference source.

    Note

    Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

RedirectToLogin component

The RedirectToLogin component (Shared/RedirectToLogin.razor):

  • Manages redirecting unauthorized users to the login page.
  • Preserves the current URL that the user is attempting to access so that they can be returned to that page if authentication is successful.
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
    protected override void OnInitialized()
    {
        Navigation.NavigateTo(
            $"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
    }
}

LoginDisplay component

The LoginDisplay component (Shared/LoginDisplay.razor) is rendered in the MainLayout component (Shared/MainLayout.razor) and manages the following behaviors:

  • For authenticated users:
    • Displays the current user name.
    • Offers a link to the user profile page in ASP.NET Core Identity.
    • Offers a button to log out of the app.
  • For anonymous users:
    • Offers the option to register.
    • Offers the option to log in.

Due to changes in the framework across releases of ASP.NET Core, Razor markup for the LoginDisplay component isn't shown in this section. To inspect the markup of the component for a given release, use either of the following approaches:

  • Create an app provisioned for authentication from the default Blazor WebAssembly project template for the version of ASP.NET Core that you intend to use. Inspect the LoginDisplay component in the generated app.

  • Inspect the LoginDisplay component in reference source. The templated content for Hosted equal to true is used.

    Note

    Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Authentication component

The page produced by the Authentication component (Pages/Authentication.razor) defines the routes required for handling different authentication stages.

The RemoteAuthenticatorView component:

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

<RemoteAuthenticatorView Action="@Action" />

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

FetchData component

The FetchData component shows how to:

  • Provision an access token.
  • Use the access token to call a protected resource API in the Server app.

The @attribute [Authorize] directive indicates to the Blazor WebAssembly authorization system that the user must be authorized in order to visit this component. The presence of the attribute in the Client app doesn't prevent the API on the server from being called without proper credentials. The Server app also must use [Authorize] on the appropriate endpoints to correctly protect them.

IAccessTokenProvider.RequestAccessToken takes care of requesting an access token that can be added to the request to call the API. If the token is cached or the service is able to provision a new access token without user interaction, the token request succeeds. Otherwise, the token request fails with an AccessTokenNotAvailableException, which is caught in a try-catch statement.

In order to obtain the actual token to include in the request, the app must check that the request succeeded by calling tokenResult.TryGetToken(out var token).

If the request was successful, the token variable is populated with the access token. The AccessToken.Value property of the token exposes the literal string to include in the Authorization request header.

If the request failed because the token couldn't be provisioned without user interaction:

  • ASP.NET Core 7.0 or later: The app navigates to AccessTokenResult.InteractiveRequestUrl using the given AccessTokenResult.InteractionOptions to allow refreshing the access token.
  • ASP.NET Core 6.0 or earlier: The token result contains a redirect URL. Navigating to this URL takes the user to the login page and back to the current page after a successful authentication.
@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Run the app

Run the app from the Server project. When using Visual Studio, either:

  • Set the Startup Projects drop down list in the toolbar to the Server API app and select the Run button.
  • Select the Server project in Solution Explorer and select the Run button in the toolbar or start the app from the Debug menu.

Name and role claim with API authorization

Custom user factory

In the Client app, create a custom user factory. Identity Server sends multiple roles as a JSON array in a single role claim. A single role is sent as a string value in the claim. The factory creates an individual role claim for each of the user's roles.

CustomUserFactory.cs:

using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);

        if (user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = account.AdditionalProperties[identity.RoleClaimType];

                if (rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
                        }
                    }
                    else
                    {
                        identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
                    }
                }
            }
        }

        return user;
    }
}

In the Client app, register the factory in Program.cs:

builder.Services.AddApiAuthorization()
    .AddAccountClaimsPrincipalFactory<CustomUserFactory>();

In the Server app, call AddRoles on the Identity builder, which adds role-related services:

using Microsoft.AspNetCore.Identity;

...

services.AddDefaultIdentity<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Configure Identity Server

Use one of the following approaches:

API authorization options

In the Server app:

  • Configure Identity Server to put the name and role claims into the ID token and access token.
  • Prevent the default mapping for roles in the JWT token handler.
using System.IdentityModel.Tokens.Jwt;
using System.Linq;

...

services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Profile Service

In the Server app, create a ProfileService implementation.

ProfileService.cs:

using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Services;
using System.Threading.Tasks;

public class ProfileService : IProfileService
{
    public ProfileService()
    {
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
        context.IssuedClaims.AddRange(nameClaim);

        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
        context.IssuedClaims.AddRange(roleClaims);

        await Task.CompletedTask;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        await Task.CompletedTask;
    }
}

In the Server app, register the Profile Service in Startup.ConfigureServices:

using IdentityServer4.Services;

...

services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Use authorization mechanisms

In the Client app, component authorization approaches are functional at this point. Any of the authorization mechanisms in components can use a role to authorize the user:

User.Identity.Name is populated in the Client app with the user's user name, which is usually their sign-in email address.

UserManager and SignInManager

Set the user identifier claim type when a Server app requires:

In Program.cs for ASP.NET Core 6.0 or later:

using System.Security.Claims;

...

builder.Services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

In Startup.ConfigureServices for versions of ASP.NET Core earlier than 6.0:

using System.Security.Claims;

...

services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

The following WeatherForecastController logs the UserName when the Get method is called:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using BlazorSample.Server.Models;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers
{
    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly UserManager<ApplicationUser> userManager;

        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", 
            "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger, 
            UserManager<ApplicationUser> userManager)
        {
            this.logger = logger;
            this.userManager = userManager;
        }

        [HttpGet]
        public async Task<IEnumerable<WeatherForecast>> Get()
        {
            var rng = new Random();

            var user = await userManager.GetUserAsync(User);

            if (user != null)
            {
                logger.LogInformation($"User.Identity.Name: {user.UserName}");
            }

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

In the preceding example:

  • The Server project's namespace is BlazorSample.Server.
  • The Shared project's namespace is BlazorSample.Shared.

Host in Azure App Service with a custom domain and certificate

The following guidance explains:

  • How to deploy a hosted Blazor WebAssembly app with Identity Server to Azure App Service with a custom domain.
  • How to create and use a TLS certificate for HTTPS protocol communication with browsers. Although the guidance focuses on using the certificate with a custom domain, the guidance is equally applicable to using a default Azure Apps domain, for example contoso.azurewebsites.net.

For this hosting scenario, do not use the same certificate for Identity Server's token signing key and the site's HTTPS secure communication with browsers:

  • Using different certificates for these two requirements is a good security practice because it isolates private keys for each purpose.
  • TLS certificates for communication with browsers is managed independently without affecting Identity Server's token signing.
  • When Azure Key Vault supplies a certificate to an App Service app for custom domain binding, Identity Server can't obtain the same certificate from Azure Key Vault for token signing. Although configuring Identity Server to use the same TLS certificate from a physical path is possible, placing security certificates into source control is a poor practice and should be avoided in most scenarios.

In the following guidance, a self-signed certificate is created in Azure Key Vault solely for Identity Server token signing. The Identity Server configuration uses the key vault certificate via the app's CurrentUser > My certificate store. Other certificates used for HTTPS traffic with custom domains are created and configured separately from the Identity Server signing certificate.

To configure an app, Azure App Service, and Azure Key Vault to host with a custom domain and HTTPS:

  1. Create an App Service plan with an plan level of Basic B1 or higher. App Service requires a Basic B1 or higher service tier to use custom domains.

  2. Create a PFX certificate for the site's secure browser communication (HTTPS protocol) with a common name of the site's fully qualified domain name (FQDN) that your organization controls (for example, www.contoso.com). Create the certificate with:

    • Key uses
      • Digital signature validation (digitalSignature)
      • Key encipherment (keyEncipherment)
    • Enhanced/extended key uses
      • Client Authentication (1.3.6.1.5.5.7.3.2)
      • Server Authentication (1.3.6.1.5.5.7.3.1)

    To create the certificate, use one of the following approaches or any other suitable tool or online service:

    Make note of the password, which is used later to import the certificate into Azure Key Vault.

    For more information on Azure Key Vault certificates, see Azure Key Vault: Certificates.

  3. Create a new Azure Key Vault or use an existing key vault in your Azure subscription.

  4. In the key vault's Certificates area, import the PFX site certificate. Record the certificate's thumbprint, which is used in the app's configuration later.

  5. In Azure Key Vault, generate a new self-signed certificate for Identity Server token signing. Give the certificate a Certificate Name and Subject. The Subject is specified as CN={COMMON NAME}, where the {COMMON NAME} placeholder is the certificate's common name. The common name can be any alphanumeric string. For example, CN=IdentityServerSigning is a valid certificate Subject. In Issuance Policy > Advanced Policy Configuration, use the default settings. Record the certificate's thumbprint, which is used in the app's configuration later.

  6. Navigate to Azure App Service in the Azure portal and create a new App Service with the following configuration:

    • Publish set to Code.
    • Runtime stack set to the app's runtime.
    • For Sku and size, confirm that the App Service tier is Basic B1 or higher. App Service requires a Basic B1 or higher service tier to use custom domains.
  7. After Azure creates the App Service, open the app's Configuration and add a new application setting specifying the certificate thumbprints recorded earlier. The app setting key is WEBSITE_LOAD_CERTIFICATES. Separate the certificate thumbprints in the app setting value with a comma, as the following example shows:

    • Key: WEBSITE_LOAD_CERTIFICATES
    • Value: 57443A552A46DB...D55E28D412B943565,29F43A772CB6AF...1D04F0C67F85FB0B1

    In the Azure portal, saving app settings is a two-step process: Save the WEBSITE_LOAD_CERTIFICATES key-value setting, then select the Save button at the top of the blade.

  8. Select the app's TLS/SSL settings. Select Private Key Certificates (.pfx). Use the Import Key Vault Certificate process. Use the process twice to import the site's certificate for HTTPS communication and the site's self-signed Identity Server token signing certificate.

  9. Navigate to the Custom domains blade. At your domain registrar's website, use the IP address and Custom Domain Verification ID to configure the domain. A typical domain configuration includes:

    • An A Record with a Host of @ and a value of the IP address from the Azure portal.
    • A TXT Record with a Host of asuid and the value of the verification ID generated by Azure and provided by the Azure portal.

    Make sure that you save the changes at your domain registrar's website correctly. Some registrar websites require a two-step process to save domain records: One or more records are saved individually followed by updating the domain's registration with a separate button.

  10. Return to the Custom domains blade in the Azure portal. Select Add custom domain. Select the A Record option. Provide the domain and select Validate. If the domain records are correct and propagated across the Internet, the portal allows you to select the Add custom domain button.

    It can take a few days for domain registration changes to propagate across Internet domain name servers (DNS) after they're processed by your domain registrar. If domain records aren't updated within three business days, confirm the records are correctly set with the domain registrar and contact their customer support.

  11. In the Custom domains blade, the SSL STATE for the domain is marked Not Secure. Select the Add binding link. Select the site HTTPS certificate from the key vault for the custom domain binding.

  12. In Visual Studio, open the Server project's app settings file (appsettings.json or appsettings.Production.json). In the Identity Server configuration, add the following Key section. Specify the self-signed certificate Subject for the Name key. In the following example, the certificate's common name assigned in the key vault is IdentityServerSigning, which yields a Subject of CN=IdentityServerSigning:

    "IdentityServer": {
    
      ...
    
      "Key": {
        "Type": "Store",
        "StoreName": "My",
        "StoreLocation": "CurrentUser",
        "Name": "CN=IdentityServerSigning"
      }
    },
    
  13. In Visual Studio, create an Azure App Service publish profile for the Server project. From the menu bar, select: Build > Publish > New > Azure > Azure App Service (Windows or Linux). When Visual Studio is connected to an Azure subscription, you can set the View of Azure resources by Resource type. Navigate within the Web App list to find the App Service for the app and select it. Select Finish.

  14. When Visual Studio returns to the Publish window, the key vault and SQL Server database service dependencies are automatically detected.

    No configuration changes to the default settings are required for the key vault service.

    For testing purposes, an app's local SQLite database, which is configured by default by the Blazor template, can be deployed with the app without additional configuration. Configuring a different database for Identity Server in production is beyond the scope of this article. For more information, see the database resources in the following documentation sets:

  15. Select the Edit link under the deployment profile name at the top of the window. Change the destination URL to the site's custom domain URL (for example, https://www.contoso.com). Save the settings.

  16. Publish the app. Visual Studio opens a browser window and requests the site at its custom domain.

The Azure documentation contains additional detail on using Azure services and custom domains with TLS binding in App Service, including information on using CNAME records instead of A records. For more information, see the following resources:

We recommend using a new in-private or incognito browser window for each app test run after a change to the app, app configuration, or Azure services in the Azure portal. Lingering cookies from a previous test run can result in failed authentication or authorization when testing the site even when the site's configuration is correct. For more information on how to configure Visual Studio to open a new in-private or incognito browser window for each test run, see the Cookies and site data section.

When App Service configuration is changed in the Azure portal, the updates generally take effect quickly but aren't instant. Sometimes, you must wait a short period for an App Service to restart in order for a configuration change to take effect.

If troubleshooting an Identity Server key-signing certificate loading problem, execute the following command in an Azure portal Kudu PowerShell command shell. The command provides a list of certificates that the app can access from the CurrentUser > My certificate store. The output includes certificate subjects and thumbprints useful when debugging an app:

Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList

Troubleshoot

Common errors

  • Misconfiguration of the app or Identity Provider (IP)

    The most common errors are caused by incorrect configuration. The following are a few examples:

    • Depending on the requirements of the scenario, a missing or incorrect Authority, Instance, Tenant ID, Tenant domain, Client ID, or Redirect URI prevents an app from authenticating clients.
    • An incorrect access token scope prevents clients from accessing server web API endpoints.
    • Incorrect or missing server API permissions prevent clients from accessing server web API endpoints.
    • Running the app at a different port than is configured in the Redirect URI of the Identity Provider's app registration.

    Configuration sections of this article's guidance show examples of the correct configuration. Carefully check each section of the article looking for app and IP misconfiguration.

    If the configuration appears correct:

    • Analyze application logs.

    • Examine the network traffic between the client app and the IP or server app with the browser's developer tools. Often, an exact error message or a message with a clue to what's causing the problem is returned to the client by the IP or server app after making a request. Developer tools guidance is found in the following articles:

    • Decode the contents of a JSON Web Token (JWT) used for authenticating a client or accessing a server web API, depending on where the problem is occurring. For more information, see Inspect the content of a JSON Web Token (JWT).

    The documentation team responds to document feedback and bugs in articles (open an issue from the This page feedback section) but is unable to provide product support. Several public support forums are available to assist with troubleshooting an app. We recommend the following:

    The preceding forums are not owned or controlled by Microsoft.

    For non-security, non-sensitive, and non-confidential reproducible framework bug reports, open an issue with the ASP.NET Core product unit. Don't open an issue with the product unit until you've thoroughly investigated the cause of a problem and can't resolve it on your own and with the help of the community on a public support forum. The product unit isn't able to troubleshoot individual apps that are broken due to simple misconfiguration or use cases involving third-party services. If a report is sensitive or confidential in nature or describes a potential security flaw in the product that attackers may exploit, see Reporting security issues and bugs (dotnet/aspnetcore GitHub repository).

  • Unauthorized client for AAD

    info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. These requirements were not met: DenyAnonymousAuthorizationRequirement: Requires an authenticated user.

    Login callback error from AAD:

    • Error: unauthorized_client
    • Description: AADB2C90058: The provided application is not configured to allow public clients.

    To resolve the error:

    1. In the Azure portal, access the app's manifest.
    2. Set the allowPublicClient attribute to null or true.

Cookies and site data

Cookies and site data can persist across app updates and interfere with testing and troubleshooting. Clear the following when making app code changes, user account changes with the provider, or provider app configuration changes:

  • User sign-in cookies
  • App cookies
  • Cached and stored site data

One approach to prevent lingering cookies and site data from interfering with testing and troubleshooting is to:

  • Configure a browser
    • Use a browser for testing that you can configure to delete all cookie and site data each time the browser is closed.
    • Make sure that the browser is closed manually or by the IDE for any change to the app, test user, or provider configuration.
  • Use a custom command to open a browser in incognito or private mode in Visual Studio:
    • Open Browse With dialog box from Visual Studio's Run button.
    • Select the Add button.
    • Provide the path to your browser in the Program field. The following executable paths are typical installation locations for Windows 10. If your browser is installed in a different location or you aren't using Windows 10, provide the path to the browser's executable.
      • Microsoft Edge: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
    • In the Arguments field, provide the command-line option that the browser uses to open in incognito or private mode. Some browsers require the URL of the app.
      • Microsoft Edge: Use -inprivate.
      • Google Chrome: Use --incognito --new-window {URL}, where the placeholder {URL} is the URL to open (for example, https://localhost:5001).
      • Mozilla Firefox: Use -private -url {URL}, where the placeholder {URL} is the URL to open (for example, https://localhost:5001).
    • Provide a name in the Friendly name field. For example, Firefox Auth Testing.
    • Select the OK button.
    • To avoid having to select the browser profile for each iteration of testing with an app, set the profile as the default with the Set as Default button.
    • Make sure that the browser is closed by the IDE for any change to the app, test user, or provider configuration.

App upgrades

A functioning app may fail immediately after upgrading either the .NET Core SDK on the development machine or changing package versions within the app. In some cases, incoherent packages may break an app when performing major upgrades. Most of these issues can be fixed by following these instructions:

  1. Clear the local system's NuGet package caches by executing dotnet nuget locals all --clear from a command shell.
  2. Delete the project's bin and obj folders.
  3. Restore and rebuild the project.
  4. Delete all of the files in the deployment folder on the server prior to redeploying the app.

Note

Use of package versions incompatible with the app's target framework isn't supported. For information on a package, use the NuGet Gallery or FuGet Package Explorer.

Run the Server app

When testing and troubleshooting a hosted Blazor WebAssembly solution, make sure that you're running the app from the Server project. For example in Visual Studio, confirm that the Server project is highlighted in Solution Explorer before you start the app with any of the following approaches:

  • Select the Run button.
  • Use Debug > Start Debugging from the menu.
  • Press F5.

Inspect the user

The ASP.NET Core framework's test assets include a Blazor WebAssembly client app with a User component that can be useful in troubleshooting. The User component can be used directly in apps or serve as the basis for further customization:

User test component in the dotnet/aspnetcore GitHub repository

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Inspect the content of a JSON Web Token (JWT)

To decode a JSON Web Token (JWT), use Microsoft's jwt.ms tool. Values in the UI never leave your browser.

Example encoded JWT (shortened for display):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1j ... bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzpD-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-yBMKV2_nXA25Q

Example JWT decoded by the tool for an app that authenticates against Azure AAD B2C:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
  "exp": 1610059429,
  "nbf": 1610055829,
  "ver": "1.0",
  "iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-226dcc9ad298/v2.0/",
  "sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
  "aud": "70bde375-fce3-4b82-984a-b247d823a03f",
  "nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
  "iat": 1610055829,
  "auth_time": 1610055822,
  "idp": "idp.com",
  "tfp": "B2C_1_signupsignin"
}.[Signature]

Additional resources