Secure an ASP.NET Core Blazor WebAssembly hosted app with Azure Active Directory B2C

This article describes how to create a hosted Blazor WebAssembly solution that uses Azure Active Directory (AAD) B2C for authentication.

Register apps in AAD B2C and create solution

Create a tenant

Follow the guidance in Tutorial: Create an Azure Active Directory B2C tenant to create an AAD B2C tenant. Return to this article immediately after creating or identifying a tenant to use.

Record the AAD B2C instance (for example, https://contoso.b2clogin.com/, which includes the trailing slash). The instance is the scheme and host of an Azure B2C app registration, which can be found by opening the Endpoints window from the App registrations page in the Azure portal.

Register a server API app

Register an AAD B2C app for the Server API app:

  1. Navigate to Azure Active Directory in the Azure portal. Select App registrations in the sidebar. Select the New registration button.
  2. Provide a Name for the app (for example, Blazor Server AAD B2C).
  3. For Supported account types, select the multi-tenant option: Accounts in any identity provider or organizational directory (for authenticating users with user flows)
  4. The Server API app doesn't require a Redirect URI in this scenario, so leave the drop down set to Web and don't enter a redirect URI.
  5. If you're using an unverified publisher domain, confirm that Permissions > Grant admin consent to openid and offline_access permissions is selected. If the publisher domain is verified, this checkbox isn't present.
  6. Select Register.

Record the following information:

  • Server API app Application (client) ID (for example, 41451fa7-82d9-4673-8fa5-69eff5a761fd)
  • AAD Primary/Publisher/Tenant domain (for example, contoso.onmicrosoft.com): The domain is available as the Publisher domain in the Branding blade of the Azure portal for the registered app.

In Expose an API:

  1. Select Add a scope.
  2. Select Save and continue.
  3. Provide a Scope name (for example, API.Access).
  4. Provide an Admin consent display name (for example, Access API).
  5. Provide an Admin consent description (for example, Allows the app to access server app API endpoints.).
  6. Confirm that the State is set to Enabled.
  7. Select Add scope.

Record the following information:

  • App ID URI (for example, api://41451fa7-82d9-4673-8fa5-69eff5a761fd, https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd, or the custom value that you provided)
  • Scope name (for example, API.Access)

Register a client app

Register an AAD B2C app for the Client app:

  1. Navigate to Azure Active Directory in the Azure portal. Select App registrations in the sidebar. Select the New registration button.

  2. Provide a Name for the app (for example, Blazor Client AAD B2C).

  3. For Supported account types, select the multi-tenant option: Accounts in any identity provider or organizational directory (for authenticating users with user flows)

  4. Set the Redirect URI drop down to Single-page application (SPA) and provide the following redirect URI: https://localhost:{PORT}/authentication/login-callback. The default port for an app running on Kestrel is 5001. If the app is run on a different Kestrel port, use the app's port. For IIS Express, the randomly generated port for the app can be found in the Server app's properties in the Debug panel. Since the app doesn't exist at this point and the IIS Express port isn't known, return to this step after the app is created and update the redirect URI. A remark appears in the Create the app section to remind IIS Express users to update the redirect URI.

  5. If you're using an unverified publisher domain, confirm that Permissions > Grant admin consent to openid and offline_access permissions is selected. If the publisher domain is verified, this checkbox isn't present.

  6. Select Register.

  7. Record the Application (client) ID (for example, 4369008b-21fa-427c-abaa-9b53bf58e538).

In Authentication > Platform configurations > Single-page application (SPA):

  1. Confirm the Redirect URI of https://localhost:{PORT}/authentication/login-callback is present.
  2. For Implicit grant, ensure that the checkboxes for Access tokens and ID tokens are not selected.
  3. The remaining defaults for the app are acceptable for this experience.
  4. Select the Save button.

In API permissions:

  1. Select Add a permission followed by My APIs.
  2. Select the Server API app from the Name column (for example, Blazor Server AAD B2C).
  3. Open the API list.
  4. Enable access to the API (for example, API.Access).
  5. Select Add permissions.
  6. Select the Grant admin consent for {TENANT NAME} button. Select Yes to confirm.

In Home > Azure AD B2C > User flows:

Create a sign-up and sign-in user flow

At a minimum, select the Application claims > Display Name user attribute to populate the context.User.Identity.Name in the LoginDisplay component (Shared/LoginDisplay.razor).

Record the sign-up and sign-in user flow name created for the app (for example, B2C_1_signupsignin).

Create the app

Replace the placeholders in the following command with the information recorded earlier and execute the command in a command shell:

dotnet new blazorwasm -au IndividualB2C --aad-b2c-instance "{AAD B2C INSTANCE}" --api-client-id "{SERVER API APP CLIENT ID}" --app-id-uri "{SERVER API APP ID URI}" --client-id "{CLIENT APP CLIENT ID}" --default-scope "{DEFAULT SCOPE}" --domain "{TENANT DOMAIN}" -ho -o {APP NAME} -ssp "{SIGN UP OR SIGN IN POLICY}"

Warning

Avoid using dashes (-) in the app name {APP 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).

Placeholder Azure portal name Example
{AAD B2C INSTANCE} Instance https://contoso.b2clogin.com/
{APP NAME} BlazorSample
{CLIENT APP CLIENT ID} Application (client) ID for the Client app 4369008b-21fa-427c-abaa-9b53bf58e538
{DEFAULT SCOPE} Scope name API.Access
{SERVER API APP CLIENT ID} Application (client) ID for the Server API app 41451fa7-82d9-4673-8fa5-69eff5a761fd
{SERVER API APP ID URI} Application ID URI† 41451fa7-82d9-4673-8fa5-69eff5a761fd
{SIGN UP OR SIGN IN POLICY} Sign-up/sign-in user flow B2C_1_signupsignin1
{TENANT DOMAIN} Primary/Publisher/Tenant domain contoso.onmicrosoft.com

†The Blazor WebAssembly template automatically adds a scheme of api:// to the App ID URI argument passed in the dotnet new command. When providing the App ID URI for the {SERVER API APP ID URI} placeholder and if the scheme is api://, remove the scheme (api://) from the argument, as the example value in the preceding table shows. If the App ID URI is a custom value or has some other scheme (for example, https:// for an unverified publisher domain similar to https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd), you must manually update the default scope URI and remove the api:// scheme after the Client app is created by the template. For more information, see the note in the Access token scopes section. The Blazor WebAssembly template might be changed in a future release of ASP.NET Core to address these scenarios. For more information, see Double scheme for App ID URI with Blazor WASM template (hosted, single org) (dotnet/aspnetcore #27417).

The output location specified with the -o|--output option creates a project folder if it doesn't exist and becomes part of the app's name. Avoid using dashes (-) in the app name that break the formation of the OIDC app identifier (see the earlier WARNING).

Note

The scope set up in a hosted Blazor WebAssembly solution by the Blazor WebAssembly project template might have the App ID URI host repeated. Confirm that the scope configured for the DefaultAccessTokenScopes collection is correct in Program.cs of the Client app.

Note

In the Azure portal, the Client app's platform configuration Redirect URI is configured for port 5001 for apps that run on the Kestrel server with default settings.

If the Client app is run on a random IIS Express port, the port for the app can be found in the Server API app's properties in the Debug panel.

If the port wasn't configured earlier with the Client app's known port, return to the Client app's registration in the Azure portal and update the redirect URI with the correct port.

Server app configuration

This section pertains to the solution's Server app.

Authentication package

The support for authenticating and authorizing calls to ASP.NET Core web APIs with the Microsoft Identity Platform is provided by the Microsoft.Identity.Web package:

<PackageReference Include="Microsoft.Identity.Web" Version="{VERSION}" />

The {VERSION} placeholder represents the latest stable version of the package that matches the app's shared framework version and can be found in the package's Version History at the NuGet Gallery.

The Server app of a hosted Blazor solution created from the Blazor WebAssembly template includes the Microsoft.Identity.Web.UI package by default. The package adds UI for user authentication in web apps and isn't used by the Blazor framework. If the Server app will never be used to authenticate users directly, it's safe to remove the package reference from the Server app's project file.

Authentication service support

The AddAuthentication method sets up authentication services within the app and configures the JWT Bearer handler as the default authentication method. The AddMicrosoftIdentityWebApi method configures services to protect the web API with Microsoft Identity Platform v2.0. This method expects an AzureAdB2C section in the app's configuration with the necessary settings to initialize authentication options.

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C"));

UseAuthentication and UseAuthorization ensure that:

  • The app attempts to parse and validate tokens on incoming requests.
  • Any request attempting to access a protected resource without proper credentials fails.
app.UseAuthentication();
app.UseAuthorization();

User.Identity.Name

By default, the User.Identity.Name isn't populated.

To configure the app to receive the value from the name claim type:

App settings

The appsettings.json file contains the options to configure the JWT bearer handler used to validate access tokens.

{
  "AzureAdB2C": {
    "Instance": "https://{TENANT}.b2clogin.com/",
    "ClientId": "{SERVER API APP CLIENT ID}",
    "Domain": "{TENANT DOMAIN}",
    "SignUpSignInPolicyId": "{SIGN UP OR SIGN IN POLICY}"
  }
}

Example:

{
  "AzureAdB2C": {
    "Instance": "https://contoso.b2clogin.com/",
    "ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
    "Domain": "contoso.onmicrosoft.com",
    "SignUpSignInPolicyId": "B2C_1_signupsignin1",
  }
}

WeatherForecast controller

The WeatherForecast controller (Controllers/WeatherForecastController.cs) exposes a protected API with the [Authorize] attribute applied to the controller. It's important to understand that:

  • The [Authorize] attribute in this API controller is the only thing that protect this API from unauthorized access.
  • The [Authorize] attribute used in the Blazor WebAssembly app only serves as a hint to the app that the user should be authorized for the app to work correctly.
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        ...
    }
}

Client app configuration

This section pertains to the solution's Client app.

Authentication package

When an app is created to use an Individual B2C Account (IndividualB2C), the app automatically receives a package reference for the Microsoft Authentication Library (Microsoft.Authentication.WebAssembly.Msal). 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 package to the app's project file:

<PackageReference Include="Microsoft.Authentication.WebAssembly.Msal" 
  Version="{VERSION}" />

For the placeholder {VERSION}, the latest stable version of the package that matches the app's shared framework version can be found in the package's Version History at NuGet.org.

The Microsoft.Authentication.WebAssembly.Msal package transitively adds the Microsoft.AspNetCore.Components.WebAssembly.Authentication package to the app.

Authentication service support

Support for HttpClient instances is added that include access tokens when making requests to the server project.

Program.cs:

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).

Support for authenticating users is registered in the service container with the AddMsalAuthentication extension method provided by the Microsoft.Authentication.WebAssembly.Msal package. This method sets up the services required for the app to interact with the Identity Provider (IP).

Program.cs:

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
    options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

The AddMsalAuthentication method accepts a callback to configure the parameters required to authenticate an app. The values required for configuring the app can be obtained from the Azure Portal AAD configuration when you register the app.

Configuration is supplied by the wwwroot/appsettings.json file:

{
  "AzureAdB2C": {
    "Authority": "{AAD B2C INSTANCE}{TENANT DOMAIN}/{SIGN UP OR SIGN IN POLICY}",
    "ClientId": "{CLIENT APP CLIENT ID}",
    "ValidateAuthority": false
  }
}

Example:

{
  "AzureAdB2C": {
    "Authority": "https://contoso.b2clogin.com/contoso.onmicrosoft.com/B2C_1_signupsignin1",
    "ClientId": "4369008b-21fa-427c-abaa-9b53bf58e538",
    "ValidateAuthority": false
  }
}

Access token scopes

The default access token scopes represent the list of access token scopes that are:

  • Included by default in the sign in request.
  • Used to provision an access token immediately after authentication.

All scopes must belong to the same app per Azure Active Directory rules. Additional scopes can be added for additional API apps as needed:

builder.Services.AddMsalAuthentication(options =>
{
    ...
    options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

Note

The Blazor WebAssembly template automatically adds a scheme of api:// to the App ID URI argument passed in the dotnet new command. When generating an app from the Blazor project template, confirm that the value of the default access token scope uses either the correct custom App ID URI value that you provided in the Azure portal or a value with one of the following formats:

  • When the publisher domain of the directory is trusted, the default access token scope is typically a value similar to the following example, where API.Access is the default scope name:

    options.ProviderOptions.DefaultAccessTokenScopes.Add(
        "api://41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access");
    

    Inspect the value for a double scheme (api://api://...). If a double scheme is present, remove the first api:// scheme from the value.

  • When the publisher domain of the directory is untrusted, the default access token scope is typically a value similar to the following example, where API.Access is the default scope name:

    options.ProviderOptions.DefaultAccessTokenScopes.Add(
        "https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access");
    

    Inspect the value for an extra api:// scheme (api://https://contoso.onmicrosoft.com/...). If an extra api:// scheme is present, remove the api:// scheme from the value.

The Blazor WebAssembly template might be changed in a future release of ASP.NET Core to address these scenarios. For more information, see Double scheme for App ID URI with Blazor WASM template (hosted, single org) (dotnet/aspnetcore #27417).

Specify additional scopes with AdditionalScopesToConsent:

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE URI}");

For more information, see the following sections of the Additional scenarios article:

Login mode

The framework defaults to pop-up login mode and falls back to redirect login mode if a pop-up can't be opened. Configure MSAL to use redirect login mode by setting the LoginMode property of MsalProviderOptions to redirect:

builder.Services.AddMsalAuthentication(options =>
{
    ...
    options.ProviderOptions.LoginMode = "redirect";
});

The default setting is popup, and the string value isn't case sensitive.

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.Authentication.WebAssembly.Msal/
    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.
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" 
                DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    @if (!context.User.Identity.IsAuthenticated)
                    {
                        <RedirectToLogin />
                    }
                    else
                    {
                        <p>
                            You are not authorized to access 
                            this resource.
                        </p>
                    }
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

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 username.
    • Offers a button to log out of the app.
  • For anonymous users, offers the option to log in.
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity.Name!
        <button class="nav-link btn btn-link" @onclick="BeginLogout">
            Log out
        </button>
    </Authorized>
    <NotAuthorized>
        <a href="authentication/login">Log in</a>
    </NotAuthorized>
</AuthorizeView>

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

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, 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.

Custom user flows

The Microsoft Authentication Library (Microsoft.Authentication.WebAssembly.Msal, NuGet package) doesn't support AAD B2C user flows by default. Create custom user flows in developer code.

For more information on how to build a challenge for a custom user flow, see User flows in Azure Active Directory B2C.

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 documenation 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:

    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 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 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

This article describes how to create a hosted Blazor WebAssembly solution that uses Azure Active Directory (AAD) B2C for authentication.

Register apps in AAD B2C and create solution

Create a tenant

Follow the guidance in Tutorial: Create an Azure Active Directory B2C tenant to create an AAD B2C tenant. Return to this article immediately after creating or identifying a tenant to use.

Record the AAD B2C instance (for example, https://contoso.b2clogin.com/, which includes the trailing slash). The instance is the scheme and host of an Azure B2C app registration, which can be found by opening the Endpoints window from the App registrations page in the Azure portal.

Register a server API app

Register an AAD B2C app for the Server API app:

  1. Navigate to Azure Active Directory in the Azure portal. Select App registrations in the sidebar. Select the New registration button.
  2. Provide a Name for the app (for example, Blazor Server AAD B2C).
  3. For Supported account types, select the multi-tenant option: Accounts in any identity provider or organizational directory (for authenticating users with user flows)
  4. The Server API app doesn't require a Redirect URI in this scenario, so leave the drop down set to Web and don't enter a redirect URI.
  5. If you're using an unverified publisher domain, confirm that Permissions > Grant admin consent to openid and offline_access permissions is selected. If the publisher domain is verified, this checkbox isn't present.
  6. Select Register.

Record the following information:

  • Server API app Application (client) ID (for example, 41451fa7-82d9-4673-8fa5-69eff5a761fd)
  • AAD Primary/Publisher/Tenant domain (for example, contoso.onmicrosoft.com): The domain is available as the Publisher domain in the Branding blade of the Azure portal for the registered app.

In Expose an API:

  1. Select Add a scope.
  2. Select Save and continue.
  3. Provide a Scope name (for example, API.Access).
  4. Provide an Admin consent display name (for example, Access API).
  5. Provide an Admin consent description (for example, Allows the app to access server app API endpoints.).
  6. Confirm that the State is set to Enabled.
  7. Select Add scope.

Record the following information:

  • App ID URI (for example, api://41451fa7-82d9-4673-8fa5-69eff5a761fd, https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd, or the custom value that you provided)
  • Scope name (for example, API.Access)

Register a client app

Register an AAD B2C app for the Client app:

  1. Navigate to Azure Active Directory in the Azure portal. Select App registrations in the sidebar. Select the New registration button.

  2. Provide a Name for the app (for example, Blazor Client AAD B2C).

  3. For Supported account types, select the multi-tenant option: Accounts in any identity provider or organizational directory (for authenticating users with user flows)

  4. Set the Redirect URI drop down to Single-page application (SPA) and provide the following redirect URI: https://localhost:{PORT}/authentication/login-callback. The default port for an app running on Kestrel is 5001. If the app is run on a different Kestrel port, use the app's port. For IIS Express, the randomly generated port for the app can be found in the Server app's properties in the Debug panel. Since the app doesn't exist at this point and the IIS Express port isn't known, return to this step after the app is created and update the redirect URI. A remark appears in the Create the app section to remind IIS Express users to update the redirect URI.

  5. If you're using an unverified publisher domain, confirm that Permissions > Grant admin consent to openid and offline_access permissions is selected. If the publisher domain is verified, this checkbox isn't present.

  6. Select Register.

  7. Record the Application (client) ID (for example, 4369008b-21fa-427c-abaa-9b53bf58e538).

In Authentication > Platform configurations > Single-page application (SPA):

  1. Confirm the Redirect URI of https://localhost:{PORT}/authentication/login-callback is present.
  2. For Implicit grant, ensure that the checkboxes for Access tokens and ID tokens are not selected.
  3. The remaining defaults for the app are acceptable for this experience.
  4. Select the Save button.

In API permissions:

  1. Select Add a permission followed by My APIs.
  2. Select the Server API app from the Name column (for example, Blazor Server AAD B2C).
  3. Open the API list.
  4. Enable access to the API (for example, API.Access).
  5. Select Add permissions.
  6. Select the Grant admin consent for {TENANT NAME} button. Select Yes to confirm.

In Home > Azure AD B2C > User flows:

Create a sign-up and sign-in user flow

At a minimum, select the Application claims > Display Name user attribute to populate the context.User.Identity.Name in the LoginDisplay component (Shared/LoginDisplay.razor).

Record the sign-up and sign-in user flow name created for the app (for example, B2C_1_signupsignin).

Create the app

Replace the placeholders in the following command with the information recorded earlier and execute the command in a command shell:

dotnet new blazorwasm -au IndividualB2C --aad-b2c-instance "{AAD B2C INSTANCE}" --api-client-id "{SERVER API APP CLIENT ID}" --app-id-uri "{SERVER API APP ID URI}" --client-id "{CLIENT APP CLIENT ID}" --default-scope "{DEFAULT SCOPE}" --domain "{TENANT DOMAIN}" -ho -o {APP NAME} -ssp "{SIGN UP OR SIGN IN POLICY}"

Warning

Avoid using dashes (-) in the app name {APP 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).

Placeholder Azure portal name Example
{AAD B2C INSTANCE} Instance https://contoso.b2clogin.com/
{APP NAME} BlazorSample
{CLIENT APP CLIENT ID} Application (client) ID for the Client app 4369008b-21fa-427c-abaa-9b53bf58e538
{DEFAULT SCOPE} Scope name API.Access
{SERVER API APP CLIENT ID} Application (client) ID for the Server API app 41451fa7-82d9-4673-8fa5-69eff5a761fd
{SERVER API APP ID URI} Application ID URI† 41451fa7-82d9-4673-8fa5-69eff5a761fd
{SIGN UP OR SIGN IN POLICY} Sign-up/sign-in user flow B2C_1_signupsignin1
{TENANT DOMAIN} Primary/Publisher/Tenant domain contoso.onmicrosoft.com

†The Blazor WebAssembly template automatically adds a scheme of api:// to the App ID URI argument passed in the dotnet new command. When providing the App ID URI for the {SERVER API APP ID URI} placeholder and if the scheme is api://, remove the scheme (api://) from the argument, as the example value in the preceding table shows. If the App ID URI is a custom value or has some other scheme (for example, https:// for an unverified publisher domain similar to https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd), you must manually update the default scope URI and remove the api:// scheme after the Client app is created by the template. For more information, see the note in the Access token scopes section. The Blazor WebAssembly template might be changed in a future release of ASP.NET Core to address these scenarios. For more information, see Double scheme for App ID URI with Blazor WASM template (hosted, single org) (dotnet/aspnetcore #27417).

The output location specified with the -o|--output option creates a project folder if it doesn't exist and becomes part of the app's name. Avoid using dashes (-) in the app name that break the formation of the OIDC app identifier (see the earlier WARNING).

Note

The scope set up in a hosted Blazor WebAssembly solution by the Blazor WebAssembly project template might have the App ID URI host repeated. Confirm that the scope configured for the DefaultAccessTokenScopes collection is correct in Program.cs of the Client app.

Note

In the Azure portal, the Client app's platform configuration Redirect URI is configured for port 5001 for apps that run on the Kestrel server with default settings.

If the Client app is run on a random IIS Express port, the port for the app can be found in the Server API app's properties in the Debug panel.

If the port wasn't configured earlier with the Client app's known port, return to the Client app's registration in the Azure portal and update the redirect URI with the correct port.

Server app configuration

This section pertains to the solution's Server app.

Authentication package

The support for authenticating and authorizing calls to ASP.NET Core web APIs with the Microsoft Identity Platform is provided by the Microsoft.Identity.Web package:

<PackageReference Include="Microsoft.Identity.Web" Version="{VERSION}" />

The {VERSION} placeholder represents the latest stable version of the package that matches the app's shared framework version and can be found in the package's Version History at the NuGet Gallery.

The Server app of a hosted Blazor solution created from the Blazor WebAssembly template includes the Microsoft.Identity.Web.UI package by default. The package adds UI for user authentication in web apps and isn't used by the Blazor framework. If the Server app will never be used to authenticate users directly, it's safe to remove the package reference from the Server app's project file.

Authentication service support

The AddAuthentication method sets up authentication services within the app and configures the JWT Bearer handler as the default authentication method. The AddMicrosoftIdentityWebApi method configures services to protect the web API with Microsoft Identity Platform v2.0. This method expects an AzureAdB2C section in the app's configuration with the necessary settings to initialize authentication options.

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C"));

UseAuthentication and UseAuthorization ensure that:

  • The app attempts to parse and validate tokens on incoming requests.
  • Any request attempting to access a protected resource without proper credentials fails.
app.UseAuthentication();
app.UseAuthorization();

User.Identity.Name

By default, the User.Identity.Name isn't populated.

To configure the app to receive the value from the name claim type:

App settings

The appsettings.json file contains the options to configure the JWT bearer handler used to validate access tokens.

{
  "AzureAdB2C": {
    "Instance": "https://{TENANT}.b2clogin.com/",
    "ClientId": "{SERVER API APP CLIENT ID}",
    "Domain": "{TENANT DOMAIN}",
    "SignUpSignInPolicyId": "{SIGN UP OR SIGN IN POLICY}"
  }
}

Example:

{
  "AzureAdB2C": {
    "Instance": "https://contoso.b2clogin.com/",
    "ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
    "Domain": "contoso.onmicrosoft.com",
    "SignUpSignInPolicyId": "B2C_1_signupsignin1",
  }
}

WeatherForecast controller

The WeatherForecast controller (Controllers/WeatherForecastController.cs) exposes a protected API with the [Authorize] attribute applied to the controller. It's important to understand that:

  • The [Authorize] attribute in this API controller is the only thing that protect this API from unauthorized access.
  • The [Authorize] attribute used in the Blazor WebAssembly app only serves as a hint to the app that the user should be authorized for the app to work correctly.
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        ...
    }
}

Client app configuration

This section pertains to the solution's Client app.

Authentication package

When an app is created to use an Individual B2C Account (IndividualB2C), the app automatically receives a package reference for the Microsoft Authentication Library (Microsoft.Authentication.WebAssembly.Msal). 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 package to the app's project file:

<PackageReference Include="Microsoft.Authentication.WebAssembly.Msal" 
  Version="{VERSION}" />

For the placeholder {VERSION}, the latest stable version of the package that matches the app's shared framework version can be found in the package's Version History at NuGet.org.

The Microsoft.Authentication.WebAssembly.Msal package transitively adds the Microsoft.AspNetCore.Components.WebAssembly.Authentication package to the app.

Authentication service support

Support for HttpClient instances is added that include access tokens when making requests to the server project.

Program.cs:

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).

Support for authenticating users is registered in the service container with the AddMsalAuthentication extension method provided by the Microsoft.Authentication.WebAssembly.Msal package. This method sets up the services required for the app to interact with the Identity Provider (IP).

Program.cs:

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
    options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

The AddMsalAuthentication method accepts a callback to configure the parameters required to authenticate an app. The values required for configuring the app can be obtained from the Azure Portal AAD configuration when you register the app.

Configuration is supplied by the wwwroot/appsettings.json file:

{
  "AzureAdB2C": {
    "Authority": "{AAD B2C INSTANCE}{TENANT DOMAIN}/{SIGN UP OR SIGN IN POLICY}",
    "ClientId": "{CLIENT APP CLIENT ID}",
    "ValidateAuthority": false
  }
}

Example:

{
  "AzureAdB2C": {
    "Authority": "https://contoso.b2clogin.com/contoso.onmicrosoft.com/B2C_1_signupsignin1",
    "ClientId": "4369008b-21fa-427c-abaa-9b53bf58e538",
    "ValidateAuthority": false
  }
}

Access token scopes

The default access token scopes represent the list of access token scopes that are:

  • Included by default in the sign in request.
  • Used to provision an access token immediately after authentication.

All scopes must belong to the same app per Azure Active Directory rules. Additional scopes can be added for additional API apps as needed:

builder.Services.AddMsalAuthentication(options =>
{
    ...
    options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

Note

The Blazor WebAssembly template automatically adds a scheme of api:// to the App ID URI argument passed in the dotnet new command. When generating an app from the Blazor project template, confirm that the value of the default access token scope uses either the correct custom App ID URI value that you provided in the Azure portal or a value with one of the following formats:

  • When the publisher domain of the directory is trusted, the default access token scope is typically a value similar to the following example, where API.Access is the default scope name:

    options.ProviderOptions.DefaultAccessTokenScopes.Add(
        "api://41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access");
    

    Inspect the value for a double scheme (api://api://...). If a double scheme is present, remove the first api:// scheme from the value.

  • When the publisher domain of the directory is untrusted, the default access token scope is typically a value similar to the following example, where API.Access is the default scope name:

    options.ProviderOptions.DefaultAccessTokenScopes.Add(
        "https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access");
    

    Inspect the value for an extra api:// scheme (api://https://contoso.onmicrosoft.com/...). If an extra api:// scheme is present, remove the api:// scheme from the value.

The Blazor WebAssembly template might be changed in a future release of ASP.NET Core to address these scenarios. For more information, see Double scheme for App ID URI with Blazor WASM template (hosted, single org) (dotnet/aspnetcore #27417).

Specify additional scopes with AdditionalScopesToConsent:

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE URI}");

For more information, see the following sections of the Additional scenarios article:

Login mode

The framework defaults to pop-up login mode and falls back to redirect login mode if a pop-up can't be opened. Configure MSAL to use redirect login mode by setting the LoginMode property of MsalProviderOptions to redirect:

builder.Services.AddMsalAuthentication(options =>
{
    ...
    options.ProviderOptions.LoginMode = "redirect";
});

The default setting is popup, and the string value isn't case sensitive.

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.Authentication.WebAssembly.Msal/
    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.
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" 
                DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    @if (!context.User.Identity.IsAuthenticated)
                    {
                        <RedirectToLogin />
                    }
                    else
                    {
                        <p>
                            You are not authorized to access 
                            this resource.
                        </p>
                    }
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

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 username.
    • Offers a button to log out of the app.
  • For anonymous users, offers the option to log in.
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity.Name!
        <button class="nav-link btn btn-link" @onclick="BeginLogout">
            Log out
        </button>
    </Authorized>
    <NotAuthorized>
        <a href="authentication/login">Log in</a>
    </NotAuthorized>
</AuthorizeView>

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

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, 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.

Custom user flows

The Microsoft Authentication Library (Microsoft.Authentication.WebAssembly.Msal, NuGet package) doesn't support AAD B2C user flows by default. Create custom user flows in developer code.

For more information on how to build a challenge for a custom user flow, see User flows in Azure Active Directory B2C.

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 documenation 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:

    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 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 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

This article describes how to create a hosted Blazor WebAssembly solution that uses Azure Active Directory (AAD) B2C for authentication.

Register apps in AAD B2C and create solution

Create a tenant

Follow the guidance in Tutorial: Create an Azure Active Directory B2C tenant to create an AAD B2C tenant. Return to this article immediately after creating or identifying a tenant to use.

Record the AAD B2C instance (for example, https://contoso.b2clogin.com/, which includes the trailing slash). The instance is the scheme and host of an Azure B2C app registration, which can be found by opening the Endpoints window from the App registrations page in the Azure portal.

Register a server API app

Register an AAD B2C app for the Server API app:

  1. Navigate to Azure Active Directory in the Azure portal. Select App registrations in the sidebar. Select the New registration button.
  2. Provide a Name for the app (for example, Blazor Server AAD B2C).
  3. For Supported account types, select the multi-tenant option: Accounts in any identity provider or organizational directory (for authenticating users with user flows)
  4. The Server API app doesn't require a Redirect URI in this scenario, so leave the drop down set to Web and don't enter a redirect URI.
  5. If you're using an unverified publisher domain, confirm that Permissions > Grant admin consent to openid and offline_access permissions is selected. If the publisher domain is verified, this checkbox isn't present.
  6. Select Register.

Record the following information:

  • Server API app Application (client) ID (for example, 41451fa7-82d9-4673-8fa5-69eff5a761fd)
  • AAD Primary/Publisher/Tenant domain (for example, contoso.onmicrosoft.com): The domain is available as the Publisher domain in the Branding blade of the Azure portal for the registered app.

In Expose an API:

  1. Select Add a scope.
  2. Select Save and continue.
  3. Provide a Scope name (for example, API.Access).
  4. Provide an Admin consent display name (for example, Access API).
  5. Provide an Admin consent description (for example, Allows the app to access server app API endpoints.).
  6. Confirm that the State is set to Enabled.
  7. Select Add scope.

Record the following information:

  • App ID URI (for example, api://41451fa7-82d9-4673-8fa5-69eff5a761fd, https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd, or the custom value that you provided)
  • Scope name (for example, API.Access)

Register a client app

Register an AAD B2C app for the Client app:

  1. Navigate to Azure Active Directory in the Azure portal. Select App registrations in the sidebar. Select the New registration button.
  2. Provide a Name for the app (for example, Blazor Client AAD B2C).
  3. For Supported account types, select the multi-tenant option: Accounts in any identity provider or organizational directory (for authenticating users with user flows)
  4. Leave the Redirect URI drop down set to Web and provide the following redirect URI: https://localhost:{PORT}/authentication/login-callback. The default port for an app running on Kestrel is 5001. If the app is run on a different Kestrel port, use the app's port. For IIS Express, the randomly generated port for the app can be found in the Server app's properties in the Debug panel. Since the app doesn't exist at this point and the IIS Express port isn't known, return to this step after the app is created and update the redirect URI. A remark appears in the Create the app section to remind IIS Express users to update the redirect URI.
  5. If you're using an unverified publisher domain, confirm that Permissions > Grant admin consent to openid and offline_access permissions is selected. If the publisher domain is verified, this checkbox isn't present.
  6. Select Register.

Record the Application (client) ID (for example, 4369008b-21fa-427c-abaa-9b53bf58e538).

In Authentication > Platform configurations > Web:

  1. Confirm the Redirect URI of https://localhost:{PORT}/authentication/login-callback is present.
  2. For Implicit grant, select the checkboxes for Access tokens and ID tokens.
  3. The remaining defaults for the app are acceptable for this experience.
  4. Select the Save button.

In API permissions:

  1. Select Add a permission followed by My APIs.
  2. Select the Server API app from the Name column (for example, Blazor Server AAD B2C).
  3. Open the API list.
  4. Enable access to the API (for example, API.Access).
  5. Select Add permissions.
  6. Select the Grant admin consent for {TENANT NAME} button. Select Yes to confirm.

In Home > Azure AD B2C > User flows:

Create a sign-up and sign-in user flow

At a minimum, select the Application claims > Display Name user attribute to populate the context.User.Identity.Name in the LoginDisplay component (Shared/LoginDisplay.razor).

Record the sign-up and sign-in user flow name created for the app (for example, B2C_1_signupsignin).

Create the app

Replace the placeholders in the following command with the information recorded earlier and execute the command in a command shell:

dotnet new blazorwasm -au IndividualB2C --aad-b2c-instance "{AAD B2C INSTANCE}" --api-client-id "{SERVER API APP CLIENT ID}" --app-id-uri "{SERVER API APP ID URI}" --client-id "{CLIENT APP CLIENT ID}" --default-scope "{DEFAULT SCOPE}" --domain "{TENANT DOMAIN}" -ho -o {APP NAME} -ssp "{SIGN UP OR SIGN IN POLICY}"

Warning

Avoid using dashes (-) in the app name {APP 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).

Placeholder Azure portal name Example
{AAD B2C INSTANCE} Instance https://contoso.b2clogin.com/
{APP NAME} BlazorSample
{CLIENT APP CLIENT ID} Application (client) ID for the Client app 4369008b-21fa-427c-abaa-9b53bf58e538
{DEFAULT SCOPE} Scope name API.Access
{SERVER API APP CLIENT ID} Application (client) ID for the Server API app 41451fa7-82d9-4673-8fa5-69eff5a761fd
{SERVER API APP ID URI} Application ID URI† 41451fa7-82d9-4673-8fa5-69eff5a761fd
{SIGN UP OR SIGN IN POLICY} Sign-up/sign-in user flow B2C_1_signupsignin1
{TENANT DOMAIN} Primary/Publisher/Tenant domain contoso.onmicrosoft.com

†The Blazor WebAssembly template automatically adds a scheme of api:// to the App ID URI argument passed in the dotnet new command. When providing the App ID URI for the {SERVER API APP ID URI} placeholder and if the scheme is api://, remove the scheme (api://) from the argument, as the example value in the preceding table shows. If the App ID URI is a custom value or has some other scheme (for example, https:// for an unverified publisher domain similar to https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd), you must manually update the default scope URI and remove the api:// scheme after the Client app is created by the template. For more information, see the note in the Access token scopes section. The Blazor WebAssembly template might be changed in a future release of ASP.NET Core to address these scenarios. For more information, see Double scheme for App ID URI with Blazor WASM template (hosted, single org) (dotnet/aspnetcore #27417).

The output location specified with the -o|--output option creates a project folder if it doesn't exist and becomes part of the app's name. Avoid using dashes (-) in the app name that break the formation of the OIDC app identifier (see the earlier WARNING).

Note

The scope set up in a hosted Blazor WebAssembly solution by the Blazor WebAssembly project template might have the App ID URI host repeated. Confirm that the scope configured for the DefaultAccessTokenScopes collection is correct in Program.cs of the Client app.

Note

In the Azure portal, the Client app's platform configuration Redirect URI is configured for port 5001 for apps that run on the Kestrel server with default settings.

If the Client app is run on a random IIS Express port, the port for the app can be found in the Server API app's properties in the Debug panel.

If the port wasn't configured earlier with the Client app's known port, return to the Client app's registration in the Azure portal and update the redirect URI with the correct port.

Server app configuration

This section pertains to the solution's Server app.

Authentication package

The support for authenticating and authorizing calls to ASP.NET Core Web APIs is provided by the Microsoft.AspNetCore.Authentication.AzureADB2C.UI package:

<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" 
  Version="{VERSION}" />

For the placeholder {VERSION}, the latest stable version of the package that matches the app's shared framework version can be found in the package's Version History at NuGet.org.

Authentication service support

The AddAuthentication method sets up authentication services within the app and configures the JWT Bearer handler as the default authentication method. The AddAzureADB2CBearer method sets up the specific parameters in the JWT Bearer handler required to validate tokens emitted by the Azure Active Directory B2C:

services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
    .AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));

UseAuthentication and UseAuthorization ensure that:

  • The app attempts to parse and validate tokens on incoming requests.
  • Any request attempting to access a protected resource without proper credentials fails.
app.UseAuthentication();
app.UseAuthorization();

User.Identity.Name

By default, the User.Identity.Name isn't populated.

To configure the app to receive the value from the name claim type:

App settings

The appsettings.json file contains the options to configure the JWT bearer handler used to validate access tokens.

{
  "AzureAdB2C": {
    "Instance": "https://{TENANT}.b2clogin.com/",
    "ClientId": "{SERVER API APP CLIENT ID}",
    "Domain": "{TENANT DOMAIN}",
    "SignUpSignInPolicyId": "{SIGN UP OR SIGN IN POLICY}"
  }
}

Example:

{
  "AzureAdB2C": {
    "Instance": "https://contoso.b2clogin.com/",
    "ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
    "Domain": "contoso.onmicrosoft.com",
    "SignUpSignInPolicyId": "B2C_1_signupsignin1",
  }
}

WeatherForecast controller

The WeatherForecast controller (Controllers/WeatherForecastController.cs) exposes a protected API with the [Authorize] attribute applied to the controller. It's important to understand that:

  • The [Authorize] attribute in this API controller is the only thing that protect this API from unauthorized access.
  • The [Authorize] attribute used in the Blazor WebAssembly app only serves as a hint to the app that the user should be authorized for the app to work correctly.
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        ...
    }
}

Client app configuration

This section pertains to the solution's Client app.

Authentication package

When an app is created to use an Individual B2C Account (IndividualB2C), the app automatically receives a package reference for the Microsoft Authentication Library (Microsoft.Authentication.WebAssembly.Msal). 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 package to the app's project file:

<PackageReference Include="Microsoft.Authentication.WebAssembly.Msal" 
  Version="{VERSION}" />

For the placeholder {VERSION}, the latest stable version of the package that matches the app's shared framework version can be found in the package's Version History at NuGet.org.

The Microsoft.Authentication.WebAssembly.Msal package transitively adds the Microsoft.AspNetCore.Components.WebAssembly.Authentication package to the app.

Authentication service support

Support for HttpClient instances is added that include access tokens when making requests to the server project.

Program.cs:

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).

Support for authenticating users is registered in the service container with the AddMsalAuthentication extension method provided by the Microsoft.Authentication.WebAssembly.Msal package. This method sets up the services required for the app to interact with the Identity Provider (IP).

Program.cs:

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
    options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

The AddMsalAuthentication method accepts a callback to configure the parameters required to authenticate an app. The values required for configuring the app can be obtained from the Azure Portal AAD configuration when you register the app.

Configuration is supplied by the wwwroot/appsettings.json file:

{
  "AzureAdB2C": {
    "Authority": "{AAD B2C INSTANCE}{TENANT DOMAIN}/{SIGN UP OR SIGN IN POLICY}",
    "ClientId": "{CLIENT APP CLIENT ID}",
    "ValidateAuthority": false
  }
}

Example:

{
  "AzureAdB2C": {
    "Authority": "https://contoso.b2clogin.com/contoso.onmicrosoft.com/B2C_1_signupsignin1",
    "ClientId": "4369008b-21fa-427c-abaa-9b53bf58e538",
    "ValidateAuthority": false
  }
}

Access token scopes

The default access token scopes represent the list of access token scopes that are:

  • Included by default in the sign in request.
  • Used to provision an access token immediately after authentication.

All scopes must belong to the same app per Azure Active Directory rules. Additional scopes can be added for additional API apps as needed:

builder.Services.AddMsalAuthentication(options =>
{
    ...
    options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

Note

The Blazor WebAssembly template automatically adds a scheme of api:// to the App ID URI argument passed in the dotnet new command. When generating an app from the Blazor project template, confirm that the value of the default access token scope uses either the correct custom App ID URI value that you provided in the Azure portal or a value with one of the following formats:

  • When the publisher domain of the directory is trusted, the default access token scope is typically a value similar to the following example, where API.Access is the default scope name:

    options.ProviderOptions.DefaultAccessTokenScopes.Add(
        "api://41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access");
    

    Inspect the value for a double scheme (api://api://...). If a double scheme is present, remove the first api:// scheme from the value.

  • When the publisher domain of the directory is untrusted, the default access token scope is typically a value similar to the following example, where API.Access is the default scope name:

    options.ProviderOptions.DefaultAccessTokenScopes.Add(
        "https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access");
    

    Inspect the value for an extra api:// scheme (api://https://contoso.onmicrosoft.com/...). If an extra api:// scheme is present, remove the api:// scheme from the value.

The Blazor WebAssembly template might be changed in a future release of ASP.NET Core to address these scenarios. For more information, see Double scheme for App ID URI with Blazor WASM template (hosted, single org) (dotnet/aspnetcore #27417).

Specify additional scopes with AdditionalScopesToConsent:

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE URI}");

For more information, see the following sections of the Additional scenarios article:

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.Authentication.WebAssembly.Msal/
    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.
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" 
                DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    @if (!context.User.Identity.IsAuthenticated)
                    {
                        <RedirectToLogin />
                    }
                    else
                    {
                        <p>
                            You are not authorized to access 
                            this resource.
                        </p>
                    }
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

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 username.
    • Offers a button to log out of the app.
  • For anonymous users, offers the option to log in.
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity.Name!
        <button class="nav-link btn btn-link" @onclick="BeginLogout">
            Log out
        </button>
    </Authorized>
    <NotAuthorized>
        <a href="authentication/login">Log in</a>
    </NotAuthorized>
</AuthorizeView>

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

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, 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.

Custom user flows

The Microsoft Authentication Library (Microsoft.Authentication.WebAssembly.Msal, NuGet package) doesn't support AAD B2C user flows by default. Create custom user flows in developer code.

For more information on how to build a challenge for a custom user flow, see User flows in Azure Active Directory B2C.

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 documenation 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:

    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 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 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