Events
Power BI DataViz World Championships
Feb 14, 4 PM - Mar 31, 4 PM
With 4 chances to enter, you could win a conference package and make it to the LIVE Grand Finale in Las Vegas
Learn moreThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Important
The Hosted Blazor WebAssembly project template was removed from the framework with the release of .NET 8 (November, 2023). The guidance in this article is only supported for .NET 7 or earlier. Hosted Blazor WebAssembly apps that are upgraded each release continue to receive product support. Alternatively, refactor the app into either a standalone Blazor WebAssembly app or a Blazor Web App.
This article explains how to create a hosted Blazor WebAssembly solution that uses Duende Identity Server to authenticate users and API calls.
Important
Duende Software might require you to pay a license fee for production use of Duende Identity Server. For more information, see Migrate from ASP.NET Core 5.0 to 6.0.
Note
To configure a standalone or hosted Blazor WebAssembly app to use an existing, external Identity Server instance, follow the guidance in Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library.
For additional security scenario coverage after reading this article, see ASP.NET Core Blazor WebAssembly additional security scenarios.
The subsections of the walkthrough explain how to:
To create a new Blazor WebAssembly project with an authentication mechanism:
Create a new project.
Choose the Blazor WebAssembly App template. Select Next.
Provide a Project name without using dashes. Confirm that the Location is correct. Select Next.
Avoid using dashes (-
) in the project name that break the formation of the OIDC app identifier. Logic in the Blazor WebAssembly project template uses the project name for an OIDC app identifier in the solution's configuration, and dashes aren't permitted in an OIDC app identifier. Pascal case (BlazorSample
) or underscores (Blazor_Sample
) are acceptable alternatives.
In the Additional information dialog, select Individual Accounts as the Authentication type to store users within the app using ASP.NET Core's Identity system.
Select the ASP.NET Core Hosted checkbox.
Select the Create button to create the app.
Run the app from the Server
project. When using Visual Studio, either:
Select the dropdown arrow next to the Run button. Open Configure Startup Projects from the dropdown list. Select the Single startup project option. Confirm or change the project for the startup project to the Server
project.
Confirm that the Server
project is highlighted in Solution Explorer before you start the app with any of the following approaches:
In a command shell, navigate to the Server
project folder of the solution. Execute the dotnet watch
(or dotnet run
) command.
This section describes the parts of a solution generated from the Blazor WebAssembly project template and describes how the solution's Client and Server projects are configured for reference. There's no specific guidance to follow in this section for a basic working application if you created the app using the guidance in the Walkthrough section. The guidance in this section is helpful for updating an app to authenticate and authorize users. However, an alternative approach to updating an app is to create a new app from the guidance in the Walkthrough section and moving the app's components, classes, and resources to the new app.
This section pertains to the solution's Server app.
The following services are registered.
In the Program
file:
Entity Framework Core and ASP.NET Core Identity:
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite( ... ));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
Identity Server with an additional AddApiAuthorization helper method that sets up default ASP.NET Core conventions on top of Identity Server:
builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
Authentication with an additional AddIdentityServerJwt helper method that configures the app to validate JWT tokens produced by Identity Server:
builder.Services.AddAuthentication()
.AddIdentityServerJwt();
In Startup.ConfigureServices
of Startup.cs
:
Entity Framework Core and ASP.NET Core Identity:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
Warning
Don't store app secrets, connection strings, credentials, passwords, personal identification numbers (PINs), private C#/.NET code, or private keys/tokens in client-side code, which is always insecure. In test/staging and production environments, server-side Blazor code and web APIs should use secure authentication flows that avoid maintaining credentials within project code or configuration files. Outside of local development testing, we recommend avoiding the use of environment variables to store sensitive data, as environment variables aren't the most secure approach. For local development testing, the Secret Manager tool is recommended for securing sensitive data. For more information, see Securely maintain sensitive data and credentials.
Identity Server with an additional AddApiAuthorization helper method that sets up default ASP.NET Core conventions on top of Identity Server:
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
Authentication with an additional AddIdentityServerJwt helper method that configures the app to validate JWT tokens produced by Identity Server:
services.AddAuthentication()
.AddIdentityServerJwt();
Note
When a single authentication scheme is registered, the authentication scheme is automatically used as the app's default scheme, and it isn't necessary to state the scheme to AddAuthentication or via AuthenticationOptions. For more information, see Overview of ASP.NET Core Authentication and the ASP.NET Core announcement (aspnet/Announcements #490).
Program
file:Startup.Configure
of Startup.cs
:The Identity Server Middleware exposes the OpenID Connect (OIDC) endpoints:
app.UseIdentityServer();
The Authentication Middleware is responsible for validating request credentials and setting the user on the request context:
app.UseAuthentication();
Authorization Middleware enables authorization capabilities:
app.UseAuthorization();
This section pertains to the solution's Server app.
The AddApiAuthorization helper method configures Identity Server for ASP.NET Core scenarios. Identity Server is a powerful and extensible framework for handling app security concerns. Identity Server exposes unnecessary complexity for the most common scenarios. Consequently, a set of conventions and configuration options is provided that we consider a good starting point. Once your authentication needs change, the full power of Identity Server is available to customize authentication to suit an app's requirements.
This section pertains to the solution's Server app.
The AddIdentityServerJwt helper method configures a policy scheme for the app as the default authentication handler. The policy is configured to allow Identity to handle all requests routed to any subpath in the Identity URL space under /Identity
. The JwtBearerHandler handles all other requests. Additionally, this method:
{PROJECT NAME}API
, where the {PROJECT NAME}
placeholder is the project's name at app creation.This section pertains to the solution's Server app.
In the WeatherForecastController
(Controllers/WeatherForecastController.cs
), the [Authorize]
attribute is applied to the class. The attribute indicates that the user must be authorized based on the default policy to access the resource. The default authorization policy is configured to use the default authentication scheme, which is set up by AddIdentityServerJwt. The helper method configures JwtBearerHandler as the default handler for requests to the app.
This section pertains to the solution's Server app.
In the ApplicationDbContext
(Data/ApplicationDbContext.cs
), DbContext extends ApiAuthorizationDbContext<TUser> to include the schema for Identity Server. ApiAuthorizationDbContext<TUser> is derived from IdentityDbContext.
To gain full control of the database schema, inherit from one of the available Identity DbContext classes and configure the context to include the Identity schema by calling builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value)
in the OnModelCreating method.
This section pertains to the solution's Server app.
In the OidcConfigurationController
(Controllers/OidcConfigurationController.cs
), the client endpoint is provisioned to serve OIDC parameters.
This section pertains to the solution's Server app.
In the app settings file (appsettings.json
) at the project root, the IdentityServer
section describes the list of configured clients. In the following example, there's a single client. The client name corresponds to the Client app's assembly name and is mapped by convention to the OAuth ClientId
parameter. The profile indicates the app type being configured. The profile is used internally to drive conventions that simplify the configuration process for the server.
"IdentityServer": {
"Clients": {
"{ASSEMBLY NAME}": {
"Profile": "IdentityServerSPA"
}
}
}
The {ASSEMBLY NAME}
placeholder is the Client app's assembly name (for example, BlazorSample.Client
).
This section pertains to the solution's Client app.
When an app is created to use Individual User Accounts (Individual
), the app automatically receives a package reference for the Microsoft.AspNetCore.Components.WebAssembly.Authentication
package. The package provides a set of primitives that help the app authenticate users and obtain tokens to call protected APIs.
If adding authentication to an app, manually add the Microsoft.AspNetCore.Components.WebAssembly.Authentication
package to the app.
Note
For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.
This section pertains to the solution's Client app.
In the Program
file, a named HttpClient is configured to supply HttpClient instances that include access tokens when making requests to the server API. At solution creation, the named HttpClient is {PROJECT NAME}.ServerAPI
, where the {PROJECT NAME}
placeholder is the project's name.
builder.Services.AddHttpClient("{PROJECT NAME}.ServerAPI",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("{PROJECT NAME}.ServerAPI"));
The {PROJECT NAME}
placeholder is the project name at solution creation. For example, providing a project name of BlazorSample
produces a named HttpClient of BlazorSample.ServerAPI
.
Note
If you're configuring a Blazor WebAssembly app to use an existing Identity Server instance that isn't part of a hosted Blazor solution, change the HttpClient base address registration from IWebAssemblyHostEnvironment.BaseAddress (builder.HostEnvironment.BaseAddress
) to the server app's API authorization endpoint URL.
This section pertains to the solution's Client app.
The support for authenticating users is plugged into the service container by the extension method provided inside the Microsoft.AspNetCore.Components.WebAssembly.Authentication
package. This method sets up the services required by the app to interact with the existing authorization system.
builder.Services.AddApiAuthorization();
Configuration for the app is loaded by convention from _configuration/{client-id}
. By convention, the client ID is set to the app's assembly name. This URL can be changed to point to a separate endpoint by calling the overload with options.
This section pertains to the solution's Client app.
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}
@using {APPLICATION ASSEMBLY}.Shared
This section pertains to the solution's Client app.
The Index page (wwwroot/index.html
) page includes a script that defines the AuthenticationService
in JavaScript. AuthenticationService
handles the low-level details of the OIDC protocol. The app internally calls methods defined in the script to perform the authentication operations.
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
This section pertains to the solution's Client app.
The App
component (App.razor
) is similar to the App
component found in Blazor Server apps:
RedirectToLogin
component.RedirectToLogin
component manages redirecting unauthorized users to the login page.Due to changes in the framework across releases of ASP.NET Core, Razor markup for the App
component (App.razor
) isn't shown in this section. To inspect the markup of the component for a given release, use either of the following approaches:
Create an app provisioned for authentication from the default Blazor WebAssembly project template for the version of ASP.NET Core that you intend to use. Inspect the App
component (App.razor
) in the generated app.
Inspect the App
component (App.razor
) in reference source. Select the version from the branch selector, and search for the component in the ProjectTemplates
folder of the repository because the App
component's location has changed over the years.
Note
Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).
This section pertains to the solution's Client app.
The RedirectToLogin
component (RedirectToLogin.razor
):
Inspect the RedirectToLogin
component in reference source. The location of the component changed over time, so use GitHub search tools to locate the component.
Note
Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).
This section pertains to the solution's Client app.
The LoginDisplay
component (LoginDisplay.razor
) is rendered in the MainLayout
component (MainLayout.razor
) and manages the following behaviors:
Due to changes in the framework across releases of ASP.NET Core, Razor markup for the LoginDisplay
component isn't shown in this section. To inspect the markup of the component for a given release, use either of the following approaches:
Create an app provisioned for authentication from the default Blazor WebAssembly project template for the version of ASP.NET Core that you intend to use. Inspect the LoginDisplay
component in the generated app.
Inspect the LoginDisplay
component in reference source. The location of the component changed over time, so use GitHub search tools to locate the component. The templated content for Hosted
equal to true
is used.
Note
Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).
This section pertains to the solution's Client app.
The page produced by the Authentication
component (Pages/Authentication.razor
) defines the routes required for handling different authentication stages.
The RemoteAuthenticatorView component:
Microsoft.AspNetCore.Components.WebAssembly.Authentication
package.@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />
@code {
[Parameter]
public string? Action { get; set; }
}
Note
Nullable reference types (NRTs) and .NET compiler null-state static analysis is supported in ASP.NET Core in .NET 6 or later. Prior to the release of ASP.NET Core in .NET 6, the string
type appears without the null type designation (?
).
This section pertains to the solution's Client app.
The FetchData
component shows how to:
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 token couldn't be provisioned without user interaction resulting in a failed request:
AccessTokenResult.InteractiveRequestUrl
using the given AccessTokenResult.InteractionOptions
to allow refreshing the access token.@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();
}
}
}
Specify the issuer explicitly when deploying to Azure App Service on Linux. For more information, see Use Identity to secure a Web API backend for SPAs.
In the Client app, create a custom user factory. Identity Server sends multiple roles as a JSON array in a single role
claim. A single role is sent as a string value in the claim. The factory creates an individual role
claim for each of the user's roles.
CustomUserFactory.cs
:
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
public class CustomUserFactory(IAccessTokenProviderAccessor accessor)
: AccountClaimsPrincipalFactory<RemoteUserAccount>(accessor)
{
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
RemoteUserAccount account,
RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
if (user.Identity is not null && user.Identity.IsAuthenticated)
{
var identity = (ClaimsIdentity)user.Identity;
var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();
if (roleClaims.Any())
{
foreach (var existingClaim in roleClaims)
{
identity.RemoveClaim(existingClaim);
}
var rolesElem =
account.AdditionalProperties[identity.RoleClaimType];
if (options.RoleClaim is not null && rolesElem is JsonElement roles)
{
if (roles.ValueKind == JsonValueKind.Array)
{
foreach (var role in roles.EnumerateArray())
{
var roleValue = role.GetString();
if (!string.IsNullOrEmpty(roleValue))
{
identity.AddClaim(
new Claim(options.RoleClaim, roleValue));
}
}
}
else
{
var roleValue = roles.GetString();
if (!string.IsNullOrEmpty(roleValue))
{
identity.AddClaim(
new Claim(options.RoleClaim, roleValue));
}
}
}
}
}
return user;
}
}
In the Client app, register the factory in the Program
file:
builder.Services.AddApiAuthorization()
.AddAccountClaimsPrincipalFactory<CustomUserFactory>();
In the Server app, call AddRoles on the Identity builder, which adds role-related services.
In the Program
file:
using Microsoft.AspNetCore.Identity;
...
builder.Services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
In Startup.cs
:
using Microsoft.AspNetCore.Identity;
...
services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Use one of the following approaches:
In the Server app:
name
and role
claims into the ID token and access token.In the Program
file:
using System.IdentityModel.Tokens.Jwt;
...
builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
In Startup.cs
:
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
...
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
In the Server app, create a ProfileService
implementation.
ProfileService.cs
:
using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
public class ProfileService : IProfileService
{
public ProfileService()
{
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
context.IssuedClaims.AddRange(nameClaim);
var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
context.IssuedClaims.AddRange(roleClaims);
await Task.CompletedTask;
}
public async Task IsActiveAsync(IsActiveContext context)
{
await Task.CompletedTask;
}
}
In the Server app, register the Profile Service in the Program
file:
using Duende.IdentityServer.Services;
...
builder.Services.AddTransient<IProfileService, ProfileService>();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
In the Server app, register the Profile Service in Startup.ConfigureServices
of Startup.cs
:
using IdentityServer4.Services;
...
services.AddTransient<IProfileService, ProfileService>();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
In the Client app, component authorization approaches are functional at this point. Any of the authorization mechanisms in components can use a role to authorize the user:
AuthorizeView
component (Example: <AuthorizeView Roles="Admin">
)
[Authorize]
attribute directive (AuthorizeAttribute) (Example: @attribute [Authorize(Roles = "Admin")]
)
Procedural logic (Example: if (user.IsInRole("Admin")) { ... }
)
Multiple role tests are supported:
if (user.IsInRole("Admin") && user.IsInRole("Developer"))
{
...
}
User.Identity.Name
is populated in the Client app with the user's user name, which is usually their sign-in email address.
Set the user identifier claim type when a Server app requires:
In Program.cs
for ASP.NET Core in .NET 6 or later:
using System.Security.Claims;
...
builder.Services.Configure<IdentityOptions>(options =>
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);
In Startup.ConfigureServices
for versions of ASP.NET Core earlier than 6.0:
using System.Security.Claims;
...
services.Configure<IdentityOptions>(options =>
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);
The following WeatherForecastController
logs the UserName when the Get
method is called.
Note
The following example uses a file-scoped namespace, which is a C# 10 or later (.NET 6 or later) feature.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using BlazorSample.Server.Models;
using BlazorSample.Shared;
namespace BlazorSample.Server.Controllers;
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly UserManager<ApplicationUser> userManager;
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm",
"Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger,
UserManager<ApplicationUser> userManager)
{
this.logger = logger;
this.userManager = userManager;
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
var rng = new Random();
var user = await userManager.GetUserAsync(User);
if (user != null)
{
logger.LogInformation("User.Identity.Name: {UserIdentityName}", user.UserName);
}
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
In the preceding example:
Server
project's namespace is BlazorSample.Server
.Shared
project's namespace is BlazorSample.Shared
.The following guidance explains:
contoso.azurewebsites.net
.For this hosting scenario, do not use the same certificate for Duende Identity Server's token signing key and the site's HTTPS secure communication with browsers:
In the following guidance, a self-signed certificate is created in Azure Key Vault solely for Identity Server token signing. The Identity Server configuration uses the key vault certificate via the app's CurrentUser
> My
certificate store. Other certificates used for HTTPS traffic with custom domains are created and configured separately from the Identity Server signing certificate.
To configure an app, Azure App Service, and Azure Key Vault to host with a custom domain and HTTPS:
Create an App Service plan with an plan level of Basic B1
or higher. App Service requires a Basic B1
or higher service tier to use custom domains.
Create a PFX certificate for the site's secure browser communication (HTTPS protocol) with a common name of the site's fully qualified domain name (FQDN) that your organization controls (for example, www.contoso.com
). Create the certificate with:
digitalSignature
)keyEncipherment
)To create the certificate, use one of the following approaches or any other suitable tool or online service:
Make note of the password, which is used later to import the certificate into Azure Key Vault.
For more information on Azure Key Vault certificates, see Azure Key Vault: Certificates.
Create a new Azure Key Vault or use an existing key vault in your Azure subscription.
In the key vault's Certificates area, import the PFX site certificate. Record the certificate's thumbprint, which is used in the app's configuration later.
In Azure Key Vault, generate a new self-signed certificate for Identity Server token signing. Give the certificate a Certificate Name and Subject. The Subject is specified as CN={COMMON NAME}
, where the {COMMON NAME}
placeholder is the certificate's common name. The common name can be any alphanumeric string. For example, CN=IdentityServerSigning
is a valid certificate Subject. In Issuance Policy > Advanced Policy Configuration, use the default settings. Record the certificate's thumbprint, which is used in the app's configuration later.
Navigate to Azure App Service in the Azure portal and create a new App Service with the following configuration:
Code
.Basic B1
or higher. App Service requires a Basic B1
or higher service tier to use custom domains.After Azure creates the App Service, open the app's Configuration and add a new application setting specifying the certificate thumbprints recorded earlier. The app setting key is WEBSITE_LOAD_CERTIFICATES
. Separate the certificate thumbprints in the app setting value with a comma, as the following example shows:
WEBSITE_LOAD_CERTIFICATES
57443A552A46DB...D55E28D412B943565,29F43A772CB6AF...1D04F0C67F85FB0B1
In the Azure portal, saving app settings is a two-step process: Save the WEBSITE_LOAD_CERTIFICATES
key-value setting, then select the Save button at the top of the blade.
Select the app's TLS/SSL settings. Select Private Key Certificates (.pfx). Use the Import Key Vault Certificate process. Use the process twice to import both the site's certificate for HTTPS communication and the site's self-signed Identity Server token signing certificate.
Navigate to the Custom domains blade. At your domain registrar's website, use the IP address and Custom Domain Verification ID to configure the domain. A typical domain configuration includes:
@
and a value of the IP address from the Azure portal.asuid
and the value of the verification ID generated by Azure and provided by the Azure portal.Make sure that you save the changes at your domain registrar's website correctly. Some registrar websites require a two-step process to save domain records: One or more records are saved individually followed by updating the domain's registration with a separate button.
Return to the Custom domains blade in the Azure portal. Select Add custom domain. Select the A Record option. Provide the domain and select Validate. If the domain records are correct and propagated across the Internet, the portal allows you to select the Add custom domain button.
It can take a few days for domain registration changes to propagate across Internet domain name servers (DNS) after they're processed by your domain registrar. If domain records aren't updated within three business days, confirm the records are correctly set with the domain registrar and contact their customer support.
In the Custom domains blade, the SSL STATE for the domain is marked Not Secure
. Select the Add binding link. Select the site HTTPS certificate from the key vault for the custom domain binding.
In Visual Studio, open the Server project's app settings file (appsettings.json
or appsettings.Production.json
). In the Identity Server configuration, add the following Key
section. Specify the self-signed certificate Subject for the Name
key. In the following example, the certificate's common name assigned in the key vault is IdentityServerSigning
, which yields a Subject of CN=IdentityServerSigning
:
"IdentityServer": {
...
"Key": {
"Type": "Store",
"StoreName": "My",
"StoreLocation": "CurrentUser",
"Name": "CN=IdentityServerSigning"
}
},
In Visual Studio, create an Azure App Service publish profile for the Server project. From the menu bar, select: Build > Publish > New > Azure > Azure App Service (Windows or Linux). When Visual Studio is connected to an Azure subscription, you can set the View of Azure resources by Resource type. Navigate within the Web App list to find the App Service for the app and select it. Select Finish.
When Visual Studio returns to the Publish window, the key vault and SQL Server database service dependencies are automatically detected.
No configuration changes to the default settings are required for the key vault service.
For testing purposes, an app's local SQLite database, which is configured by the Blazor template, can be deployed with the app without additional configuration. Configuring a different database for Identity Server in production is beyond the scope of this article. For more information, see the database resources in the following documentation sets:
Select the Edit link under the deployment profile name at the top of the window. Change the destination URL to the site's custom domain URL (for example, https://www.contoso.com
). Save the settings.
Publish the app. Visual Studio opens a browser window and requests the site at its custom domain.
The Azure documentation contains additional detail on using Azure services and custom domains with TLS binding in App Service, including information on using CNAME records instead of A records. For more information, see the following resources:
We recommend using a new private mode browser window (for example, Microsoft Edge InPrivate mode or Google Chrome Incognito mode) for each app test run after a change to the app, app configuration, or Azure services in the Azure portal. Lingering cookies from a previous test run can result in failed authentication or authorization when testing the site even when the site's configuration is correct. For more information on how to configure Visual Studio to open a new private browser window for each test run, see the Cookies and site data section.
When App Service configuration is changed in the Azure portal, the updates generally take effect quickly but aren't instant. Sometimes, you must wait a short period for an App Service to restart in order for a configuration change to take effect.
If troubleshooting an Identity Server key-signing certificate loading problem, execute the following command in an Azure portal Kudu PowerShell command shell. The command provides a list of certificates that the app can access from the CurrentUser
> My
certificate store. The output includes certificate subjects and thumbprints useful when debugging an app:
Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList
To enable debug or trace logging for Blazor WebAssembly authentication, see the Client-side authentication logging section of ASP.NET Core Blazor logging with the article version selector set to ASP.NET Core 7.0 or later.
Misconfiguration of the app or Identity Provider (IP)
The most common errors are caused by incorrect configuration. The following are a few examples:
localhost
development testing address, but the app's port configuration and the port where the app is running must match for non-localhost
addresses.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:
For releases of Blazor where a JSON Web Token (JWT) is used, decode the contents of the token used for authenticating a client or accessing a server web API, depending on where the problem is occurring. For more information, see Inspect the content of a JSON Web Token (JWT).
The documentation team responds to document feedback and bugs in articles (open an issue from the This page feedback section) but is unable to provide product support. Several public support forums are available to assist with troubleshooting an app. We recommend the following:
The preceding forums are not owned or controlled by Microsoft.
For non-security, non-sensitive, and non-confidential reproducible framework bug reports, open an issue with the ASP.NET Core product unit. Don't open an issue with the product unit until you've thoroughly investigated the cause of a problem and can't resolve it on your own and with the help of the community on a public support forum. The product unit isn't able to troubleshoot individual apps that are broken due to simple misconfiguration or use cases involving third-party services. If a report is sensitive or confidential in nature or describes a potential security flaw in the product that cyberattackers may exploit, see Reporting security issues and bugs (dotnet/aspnetcore
GitHub repository).
Unauthorized client for ME-ID
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. These requirements were not met: DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
Login callback error from ME-ID:
unauthorized_client
AADB2C90058: The provided application is not configured to allow public clients.
To resolve the error:
allowPublicClient
attribute to null
or true
.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:
One approach to prevent lingering cookies and site data from interfering with testing and troubleshooting is to:
C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
C:\Program Files\Mozilla Firefox\firefox.exe
-inprivate
.--incognito --new-window {URL}
, where the {URL}
placeholder is the URL to open (for example, https://localhost:5001
).-private -url {URL}
, where the {URL}
placeholder is the URL to open (for example, https://localhost:5001
).Firefox Auth Testing
.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:
dotnet nuget locals all --clear
from a command shell.bin
and obj
folders.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.
When testing and troubleshooting a hosted Blazor WebAssembly solution, make sure that you're running the app from the Server
project.
The following User
component can be used directly in apps or serve as the basis for further customization.
User.razor
:
@page "/user"
@attribute [Authorize]
@using System.Text.Json
@using System.Security.Claims
@inject IAccessTokenProvider AuthorizationService
<h1>@AuthenticatedUser?.Identity?.Name</h1>
<h2>Claims</h2>
@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())
{
<p class="claim">@(claim.Type): @claim.Value</p>
}
<h2>Access token</h2>
<p id="access-token">@AccessToken?.Value</p>
<h2>Access token claims</h2>
@foreach (var claim in GetAccessTokenClaims())
{
<p>@(claim.Key): @claim.Value.ToString()</p>
}
@if (AccessToken != null)
{
<h2>Access token expires</h2>
<p>Current time: <span id="current-time">@DateTimeOffset.Now</span></p>
<p id="access-token-expires">@AccessToken.Expires</p>
<h2>Access token granted scopes (as reported by the API)</h2>
@foreach (var scope in AccessToken.GrantedScopes)
{
<p>Scope: @scope</p>
}
}
@code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationState { get; set; }
public ClaimsPrincipal AuthenticatedUser { get; set; }
public AccessToken AccessToken { get; set; }
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
var state = await AuthenticationState;
var accessTokenResult = await AuthorizationService.RequestAccessToken();
if (!accessTokenResult.TryGetToken(out var token))
{
throw new InvalidOperationException(
"Failed to provision the access token.");
}
AccessToken = token;
AuthenticatedUser = state.User;
}
protected IDictionary<string, object> GetAccessTokenClaims()
{
if (AccessToken == null)
{
return new Dictionary<string, object>();
}
// header.payload.signature
var payload = AccessToken.Value.Split(".")[1];
var base64Payload = payload.Replace('-', '+').Replace('_', '/')
.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');
return JsonSerializer.Deserialize<IDictionary<string, object>>(
Convert.FromBase64String(base64Payload));
}
}
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/11112222-bbbb-3333-cccc-4444dddd5555/v2.0/",
"sub": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
"aud": "00001111-aaaa-2222-bbbb-3333cccc4444",
"nonce": "bbbb0000-cccc-1111-dddd-2222eeee3333",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]
ASP.NET Core feedback
ASP.NET Core is an open source project. Select a link to provide feedback:
Events
Power BI DataViz World Championships
Feb 14, 4 PM - Mar 31, 4 PM
With 4 chances to enter, you could win a conference package and make it to the LIVE Grand Finale in Las Vegas
Learn moreTraining
Module
Show a user's emails in an ASP.NET Core app with Microsoft Graph - Training
Learn how to show a user's emails in ASP.NET Core apps with Microsoft Graph. Also, learn how to optimize Microsoft Graph queries and load email in batches.