此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 对于此处提供的信息,Microsoft 不作任何明示或暗示的担保。
对于当前版本,请参阅
本文的 .NET 7 版本
。
本文介绍 Blazor WebAssembly 应用的其他安全方案。
将令牌附加到传出请求
AuthorizationMessageHandler
是用于处理访问令牌的
DelegatingHandler
。 令牌是使用由框架注册的
IAccessTokenProvider
服务获取的。 如果无法获取令牌,则会引发
AccessTokenNotAvailableException
。
AccessTokenNotAvailableException
的
Redirect
方法使用给定的
AccessTokenResult.InteractionOptions
导航到
AccessTokenResult.InteractiveRequestUrl
以允许刷新访问令牌。
为了方便起见,框架提供
BaseAddressAuthorizationMessageHandler
,它预先配置了应用基址作为授权的 URL。
仅当请求 URI 在应用的基 URI 中时,才会添加访问令牌。
当传出请求 URI 不在应用的基 URI 中时,请使用
自定义
AuthorizationMessageHandler
类(推荐)
或
配置
AuthorizationMessageHandler
。
除了用于服务器 API 访问的客户端应用配置之外,在客户端和服务器不位于同一基址时,服务器 API 还必须允许跨域请求 (CORS)。 有关服务器端 CORS 配置的详细信息,请参阅本文后面的
跨域资源共享 (CORS)
部分。
如下示例中:
AddHttpClient
将
IHttpClientFactory
和相关服务添加到服务集合中,并配置已命名的
HttpClient
(
WebAPI
)。
HttpClient.BaseAddress
是发送请求时资源 URI 的基址。
IHttpClientFactory
由
Microsoft.Extensions.Http
NuGet 包提供。
BaseAddressAuthorizationMessageHandler
是用于处理访问令牌的
DelegatingHandler
。 仅当请求 URI 在应用的基 URI 中时,才会添加访问令牌。
IHttpClientFactory.CreateClient
使用与已命名的
HttpClient
(
WebAPI
) 相对应的配置来创建和配置
HttpClient
实例。
在下面的示例中,
HttpClientFactoryServiceCollectionExtensions.AddHttpClient
是
Microsoft.Extensions.Http
中的扩展。 将包添加到尚未引用它的应用。
有关将包添加到 .NET 应用的指南,请参阅
包使用工作流(NuGet 文档)
中“安装和管理包”下的文章。 在
NuGet.org
中确认正确的包版本。
using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
builder.Services.AddHttpClient("WebAPI",
client => client.BaseAddress = new Uri("https://www.example.com/base"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("WebAPI"));
对于基于Blazor WebAssembly 项目模板的托管Blazor解决方案,默认情况下,请求 URI 位于应用的基本 URI 中。 因此,IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)
) 被分配给从项目模板生成的应用中的 HttpClient.BaseAddress。
配置的 HttpClient 用于使用 try-catch
模式发出授权的请求:
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http
protected override async Task OnInitializedAsync()
var examples =
await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");
catch (AccessTokenNotAvailableException exception)
exception.Redirect();
自定义身份验证请求方案
以下方案演示如何自定义身份验证请求,以及如何从身份验证选项中获取登录路径。
自定义登录过程
在 InteractiveRequestOptions 的新实例上,使用一次或多次以下方法管理登录请求的其他参数:
TryAddAdditionalParameter
TryRemoveAdditionalParameter
TryGetAdditionalParameter
在以下 LoginDisplay
组件示例中,将其他参数添加到登录请求:
将 prompt
设置为 login
:强制用户在该请求上输入其凭据,从而取消单一登录。
将 loginHint
设置为 peter@contoso.com
:将 peter@contoso.com
用户的登录页预先填充用户名/电子邮件地址字段。 通常,应用会在重新身份验证期间使用此参数,并且已经使用 preferred_username
声明从前次登录提取用户名。
Shared/LoginDisplay.razor
:
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
<AuthorizeView>
<Authorized>
Hello, @context.User.Identity?.Name!
<button @onclick="BeginLogOut">Log out</button>
</Authorized>
<NotAuthorized>
<button @onclick="BeginLogIn">Log in</button>
</NotAuthorized>
</AuthorizeView>
@code{
public void BeginLogOut()
Navigation.NavigateToLogout("authentication/logout");
public void BeginLogIn()
InteractiveRequestOptions requestOptions =
new()
Interaction = InteractionType.SignIn,
ReturnUrl = Navigation.Uri,
requestOptions.TryAddAdditionalParameter("prompt", "login");
requestOptions.TryAddAdditionalParameter("loginHint", "peter@contoso.com");
Navigation.NavigateToLogin("authentication/login", requestOptions);
有关更多信息,请参见以下资源:
InteractiveRequestOptions
弹出请求参数列表
在以交互方式获取令牌之前自定义选项
如果发生 AccessTokenNotAvailableException,请在 InteractiveRequestOptions 的新实例上使用一次或多次以下方法来管理新标识提供者访问令牌请求的其他参数:
TryAddAdditionalParameter
TryRemoveAdditionalParameter
TryGetAdditionalParameter
在通过 Web API 获取 JSON 数据的以下示例中,如果访问令牌不可用(引发了 AccessTokenNotAvailableException),则为向重定向请求添加其他参数:
将 prompt
设置为 login
:强制用户在该请求上输入其凭据,从而取消单一登录。
将 loginHint
设置为 peter@contoso.com
:将 peter@contoso.com
用户的登录页预先填充用户名/电子邮件地址字段。 通常,应用会在重新身份验证期间使用此参数,并且已经使用 preferred_username
声明从前次登录提取用户名。
var examples = await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");
catch (AccessTokenNotAvailableException ex)
ex.Redirect(requestOptions => {
requestOptions.TryAddAdditionalParameter("prompt", "login");
requestOptions.TryAddAdditionalParameter("loginHint", "peter@contoso.com");
上述示例假定以下情况:
Microsoft.AspNetCore.Components.WebAssembly.Authentication 命名空间中存在 API 的 @using
/using
语句。
HttpClient
作为 Http
注入。
有关详细信息,请参阅以下资源:
InteractiveRequestOptions
重定向请求参数列表
使用 IAccessTokenProvider
时自定义选项
如果使用 IAccessTokenProvider 时获取令牌失败,请在 InteractiveRequestOptions 的新实例上使用一次或多次以下方法来管理新标识提供者访问令牌请求的其他参数:
TryAddAdditionalParameter
TryRemoveAdditionalParameter
TryGetAdditionalParameter
在尝试为用户获取访问令牌的以下示例中,如果在调用 TryGetToken 时获取令牌的尝试失败,则会向登录请求添加其他参数:
将 prompt
设置为 login
:强制用户在该请求上输入其凭据,从而取消单一登录。
将 loginHint
设置为 peter@contoso.com
:将 peter@contoso.com
用户的登录页预先填充用户名/电子邮件地址字段。 通常,应用会在重新身份验证期间使用此参数,并且已经使用 preferred_username
声明从前次登录提取用户名。
var tokenResult = await TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions
Scopes = new[] { ... }
if (!tokenResult.TryGetToken(out var token))
tokenResult.InteractionOptions.TryAddAdditionalParameter("prompt", "login");
tokenResult.InteractionOptions.TryAddAdditionalParameter("loginHint",
"peter@contoso.com");
Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl,
accessTokenResult.InteractionOptions);
上述示例假定以下情况:
Microsoft.AspNetCore.Components.WebAssembly.Authentication 命名空间中存在 API 的 @using
/using
语句。
IAccessTokenProvider 作为 TokenProvider
注入。
有关详细信息,请参阅以下资源:
InteractiveRequestOptions
弹出请求参数列表
使用自定义返回 URL 注销
下例注销用户并将用户返回到 /goodbye
终结点:
Navigation.NavigateToLogout("authentication/logout", "goodbye");
从身份验证选项获取登录路径
从 RemoteAuthenticationOptions 获取配置的登录路径:
var loginPath =
RemoteAuthOptions.Get(Options.DefaultName).AuthenticationPaths.LogInPath;
上述示例假定以下情况:
以下命名空间中存在 API 的 @using
/using
语句:using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigation)
: base(provider, navigation)
ConfigureHandler(
authorizedUrls: new[] { "https://www.example.com/base" },
scopes: new[] { "example.read", "example.write" });
在上面的代码中,作用域 example.read
和 example.write
是一般示例,不是为了反映任何特定提供程序的有效作用域。
在 Program.cs
中,CustomAuthorizationMessageHandler
注册为临时服务,并配置为由已命名的 HttpClient 发出的传出 HttpResponseMessage 实例的 DelegatingHandler。
在下面的示例中,HttpClientFactoryServiceCollectionExtensions.AddHttpClient 是 Microsoft.Extensions.Http 中的扩展。 将包添加到尚未引用它的应用。
有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。
builder.Services.AddTransient<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient("WebAPI",
client => client.BaseAddress = new Uri("https://www.example.com/base"))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
在前面的示例中,CustomAuthorizationMessageHandler
DelegatingHandler 被注册为 AddHttpMessageHandler 的临时服务。 建议对 IHttpClientFactory 进行临时注册,用于管理其自己的 DI 范围。 有关详细信息,请参阅以下资源:
用于管理 DI 范围的实用工具基组件类
在 Blazor WebAssembly 应用中检测暂时性可释放对象
对于基于 Blazor WebAssembly 项目模板的托管 Blazor 解决方案,默认情况下,IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)
) 会分配给 HttpClient.BaseAddress。
配置的 HttpClient 用于使用 try-catch
模式发出授权的请求。 其中使用 CreateClient(Microsoft.Extensions.Http
包)创建客户端,在向服务器 API 发出请求时,将向 HttpClient 提供包含访问令牌的实例。 如果请求 URI 是相对 URI,如以下示例 (ExampleAPIMethod
) 中所示,则当客户端应用发出请求时,它将与 BaseAddress 结合使用:
@inject IHttpClientFactory ClientFactory
@code {
protected override async Task OnInitializedAsync()
var client = ClientFactory.CreateClient("WebAPI");
var examples =
await client.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");
catch (AccessTokenNotAvailableException exception)
exception.Redirect();
可以使用 ConfigureHandler 方法将 AuthorizationMessageHandler 配置为授权的 URL、作用域和返回 URL。 ConfigureHandler 配置此处理程序,以使用访问令牌授权出站 HTTP 请求。 仅当至少有一个授权 URL 是请求 URI (HttpRequestMessage.RequestUri) 的基 URI 时,才附加访问令牌。 如果请求 URI 是相对 URI,则将与 BaseAddress 结合使用。
在下面的示例中,AuthorizationMessageHandler 在 Program.cs
中配置 HttpClient:
using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
builder.Services.AddScoped(sp => new HttpClient(
sp.GetRequiredService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: new[] { "https://www.example.com/base" },
scopes: new[] { "example.read", "example.write" }))
BaseAddress = new Uri("https://www.example.com/base")
在上面的代码中,作用域 example.read
和 example.write
是一般示例,不是为了反映任何特定提供程序的有效作用域。
对于基于 Blazor WebAssembly 项目模板的托管 Blazor 解决方案,默认情况下,IWebAssemblyHostEnvironment.BaseAddress 会分配给:
HttpClient.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)
)。
authorizedUrls
数组的 URL。
类型化 HttpClient
可以定义类型化的客户端,以使用它处理单个类中的所有 HTTP 和令牌获取问题。
WeatherForecastClient.cs
:
using System.Net.Http.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {ASSEMBLY NAME}.Data;
public class WeatherForecastClient
private readonly HttpClient http;
private WeatherForecast[]? forecasts;
public WeatherForecastClient(HttpClient http)
this.http = http;
public async Task<WeatherForecast[]> GetForecastAsync()
forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
"WeatherForecast");
catch (AccessTokenNotAvailableException exception)
exception.Redirect();
return forecasts ?? Array.Empty<WeatherForecast>();
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {ASSEMBLY NAME}.Data;
public class WeatherForecastClient
private readonly HttpClient http;
private WeatherForecast[] forecasts;
public WeatherForecastClient(HttpClient http)
this.http = http;
public async Task<WeatherForecast[]> GetForecastAsync()
forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
"WeatherForecast");
catch (AccessTokenNotAvailableException exception)
exception.Redirect();
return forecasts ?? Array.Empty<WeatherForecast>();
在前面的示例中,WeatherForecast
类型是保存天气预报数据的静态类。 占位符 {ASSEMBLY NAME}
是应用的程序集名称(例如 using static BlazorSample.Data;
)。
在下面的示例中,HttpClientFactoryServiceCollectionExtensions.AddHttpClient 是 Microsoft.Extensions.Http 中的扩展。 将包添加到尚未引用它的应用。
有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。
在 Program.cs
中:
using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
builder.Services.AddHttpClient<WeatherForecastClient>(
client => client.BaseAddress = new Uri("https://www.example.com/base"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
对于基于 Blazor WebAssembly 项目模板的托管 Blazor 解决方案,默认情况下,IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)
) 会分配给 HttpClient.BaseAddress。
FetchData
组件 (Pages/FetchData.razor
):
@inject WeatherForecastClient Client
protected override async Task OnInitializedAsync()
forecasts = await Client.GetForecastAsync();
可以使用 ConfigureHandler 为出站 HTTP 请求进一步配置处理程序。
在下面的示例中,HttpClientFactoryServiceCollectionExtensions.AddHttpClient 是 Microsoft.Extensions.Http 中的扩展。 将包添加到尚未引用它的应用。
有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。
在 Program.cs
中:
builder.Services.AddHttpClient<WeatherForecastClient>(
client => client.BaseAddress = new Uri("https://www.example.com/base"))
.AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: new [] { "https://www.example.com/base" },
scopes: new[] { "example.read", "example.write" }));
在上面的代码中,作用域 example.read
和 example.write
是一般示例,不是为了反映任何特定提供程序的有效作用域。
对于基于 Blazor WebAssembly 项目模板的托管 Blazor 解决方案,默认情况下,IWebAssemblyHostEnvironment.BaseAddress 会分配给:
HttpClient.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)
)。
authorizedUrls
数组的 URL。
使用安全默认客户端的应用中未经身份验证或未经授权的 Web API 请求
通常使用安全的默认 HttpClient 的应用还可以通过配置命名的 HttpClient 来发出未经身份验证或未经授权的 Web API 请求。
在下面的示例中,HttpClientFactoryServiceCollectionExtensions.AddHttpClient 是 Microsoft.Extensions.Http 中的扩展。 将包添加到尚未引用它的应用。
有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。
在 Program.cs
中:
builder.Services.AddHttpClient("WebAPI.NoAuthenticationClient",
client => client.BaseAddress = new Uri("https://www.example.com/base"));
对于基于 Blazor WebAssembly 项目模板的托管 Blazor 解决方案,默认情况下,IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)
) 会分配给 HttpClient.BaseAddress。
先前的注册是对现有安全默认 HttpClient 注册的补充。
组件从 IHttpClientFactory(Microsoft.Extensions.Http
包)创建 HttpClient以发出未经身份验证或未经授权的请求:
@inject IHttpClientFactory ClientFactory
@code {
protected override async Task OnInitializedAsync()
var client = ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");
var examples = await client.GetFromJsonAsync<ExampleType[]>(
"ExampleNoAuthentication");
服务器 API 中的控制器(上述示例中为 ExampleNoAuthenticationController
)未标记 [Authorize]
特性。
开发人员决定使用安全客户端还是使用不安全的客户端作为默认 HttpClient 实例。 做出此决定的一种方法是,考虑应用联系的经过身份验证的终结点数与未经过身份验证的终结点数。 如果应用的大部分请求都要保护 API 终结点,请使用经过身份验证的 HttpClient 实例作为默认实例。 否则,将未经身份验证的 HttpClient 实例注册为默认实例。
使用 IHttpClientFactory 的另一种方法是创建类型化客户端,以便对匿名终结点进行未经身份验证的访问。
请求其他访问令牌
可以通过调用 IAccessTokenProvider.RequestAccessToken 手动获取访问令牌。 在下面的示例中,应用需要一个额外的作用域作为默认 HttpClient。 Microsoft 身份验证库 (MSAL) 示例使用 MsalProviderOptions
配置作用域:
在 Program.cs
中:
builder.Services.AddMsalAuthentication(options =>
options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 1}");
options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 2}");
上例中的 {CUSTOM SCOPE 1}
和 {CUSTOM SCOPE 2}
占位符是自定义作用域。
IAccessTokenProvider.RequestAccessToken 方法提供重载,该重载允许应用使用一组给定的作用域来配置访问令牌。
在 Razor 组件中:
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
var tokenResult = await TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions
Scopes = new[] { "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" }
if (tokenResult.TryGetToken(out var token))
上例中的 {CUSTOM SCOPE 1}
和 {CUSTOM SCOPE 2}
占位符是自定义作用域。
AccessTokenResult.TryGetToken 返回:
如果使用 token
,则为 true
。
如果未检索到令牌,则为 false
。
跨域资源共享 (CORS)
在 CORS 请求中发送凭据(授权 cookie/标头)时,CORS 策略必须允许使用 Authorization
标头。
以下策略包括的配置用于:
请求来源(http://localhost:5000
、https://localhost:5001
)。
Any 方法(谓词)。
Content-Type
和 Authorization
标头。 若要允许使用自定义标头(例如 x-custom-header
),请在调用 WithHeaders 时列出标头。
由客户端 JavaScript 代码设置的凭据(credentials
属性设置为 include
)。
app.UseCors(policy =>
policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
.AllowAnyMethod()
.WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization,
"x-custom-header")
.AllowCredentials());
基于 Blazor WebAssembly 项目模板的托管 Blazor 解决方案对客户端和服务器应用使用相同的基址。 默认情况下,将客户端应用的 HttpClient.BaseAddress 设置为 builder.HostEnvironment.BaseAddress
的 URI。 CORS 配置不是托管 Blazor 解决方案默认配置中的必需配置。 不由服务器项目托管,并且不共享服务器应用的基址的其他客户端应用在服务器项目中需要 CORS 配置。
有关详细信息,请参阅在 ASP.NET Core 中启用跨源请求 (CORS) 和示例应用的 HTTP 请求测试器组件 (Components/HTTPRequestTester.razor
)。
处理令牌请求错误
当单页应用程序 (SPA) 使用 Open ID Connect (OIDC) 对用户进行身份验证时,身份验证状态将以会话 cookie(因用户提供其凭据而设置)的形式在 SPA 内和 Identity 提供者 (IP) 中进行本地维护。
IP 为用户发出的令牌通常在短时间(约 1 小时)内有效,因此客户端应用必须定期提取新令牌。 否则,在授予的令牌到期后,将注销用户。 在大多数情况下,由于身份验证状态或“会话”保留在 IP 中,因此 OIDC 客户端能够预配新令牌,而无需用户再次进行身份验证。
在某些情况下,如果没有用户交互,客户端就无法获得令牌(例如,当用户出于某种原因而明确从 IP 注销时)。 如果用户访问 https://login.microsoftonline.com
并注销,则会发生这种情况。在这些场景下,应用不会立即知道用户已注销。客户端持有的任何令牌都可能不再有效。 另外,当前令牌过期后,如果没有用户交互,客户端将无法预配新令牌。
这些场景并不特定于基于令牌的身份验证。 它们本就是 SPA 的一部分。 如果删除了身份验证cookie,则使用 cookie 的 SPA 也无法调用服务器 API。
当应用对受保护的资源执行 API 调用时,请注意以下事项:
要预配新的访问令牌来调用 API,可能需要用户再次进行身份验证。
即使客户端拥有看似有效的令牌,对服务器的调用也可能会失败,因为该令牌已由用户撤销。
当应用请求令牌时,有两种可能的结果:
请求成功,应用拥有有效的令牌。
请求失败,应用必须再次验证用户身份才能获得新令牌。
当令牌请求失败时,需要决定在执行重定向之前是否要保存任何当前状态。 随着复杂程度的提高,可以使用以下几种方法来存储状态:
将当前页面状态存储在会话存储中。 在 OnInitializedAsync
生命周期方法 (OnInitializedAsync) 期间,检查状态是否可以还原,然后再继续执行操作。
添加查询字符串参数,并使用该参数向应用发送信号,而该应用需要重新水化先前保存的状态。
添加具有唯一标识符的查询字符串参数以将数据存储在会话存储中,而不会导致与其他项发生冲突。
在进行身份验证操作之前使用会话存储保存应用状态
以下示例介绍如何:
在重定向到登录页面之前保留状态。
使用查询字符串参数恢复先前经过身份验证的状态。
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
@inject IJSRuntime JS
@inject NavigationManager Navigation
<EditForm Model="User" @onsubmit="OnSaveAsync">
<label>User
<InputText @bind-Value="User.Name" />
</label>
<label>Last name
<InputText @bind-Value="User.LastName" />
</label>
</EditForm>
@code {
public class Profile
public string? Name { get; set; }
public string? LastName { get; set; }
public Profile User { get; set; } = new Profile();
protected override async Task OnInitializedAsync()
var currentQuery = new Uri(Navigation.Uri).Query;
if (currentQuery.Contains("state=resumeSavingProfile"))
User = await JS.InvokeAsync<Profile>("sessionStorage.getItem",
"resumeSavingProfile");
public async Task OnSaveAsync()
var http = new HttpClient();
http.BaseAddress = new Uri(Navigation.BaseUri);
var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";
var tokenResult = await TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions
ReturnUrl = resumeUri
if (tokenResult.TryGetToken(out var token))
http.DefaultRequestHeaders.Add("Authorization",
$"Bearer {token.Value}");
await http.PostAsJsonAsync("Save", User);
await JS.InvokeVoidAsync("sessionStorage.setItem",
"resumeSavingProfile", User);
Navigation.NavigateTo(tokenResult.InteractiveRequestUrl);
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
@inject IJSRuntime JS
@inject NavigationManager Navigation
<EditForm Model="User" @onsubmit="OnSaveAsync">
<label>User
<InputText @bind-Value="User.Name" />
</label>
<label>Last name
<InputText @bind-Value="User.LastName" />
</label>
</EditForm>
@code {
public class Profile
public string Name { get; set; }
public string LastName { get; set; }
public Profile User { get; set; } = new Profile();
protected override async Task OnInitializedAsync()
var currentQuery = new Uri(Navigation.Uri).Query;
if (currentQuery.Contains("state=resumeSavingProfile"))
User = await JS.InvokeAsync<Profile>("sessionStorage.getItem",
"resumeSavingProfile");
public async Task OnSaveAsync()
var http = new HttpClient();
http.BaseAddress = new Uri(Navigation.BaseUri);
var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";
var tokenResult = await TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions
ReturnUrl = resumeUri
if (tokenResult.TryGetToken(out var token))
http.DefaultRequestHeaders.Add("Authorization",
$"Bearer {token.Value}");
await http.PostAsJsonAsync("Save", User);
await JS.InvokeVoidAsync("sessionStorage.setItem",
"resumeSavingProfile", User);
Navigation.NavigateTo(tokenResult.InteractiveRequestUrl);
在进行身份验证操作之前使用会话存储和状态容器保存应用状态
在身份验证操作过程中,有时要在将浏览器重定向到 IP 之前保存应用状态。 其中一种情况是使用状态容器并希望在身份验证成功后恢复状态。 可以使用自定义身份验证状态对象来保留特定于应用的状态或对其的引用,并在身份验证操作成功完成期间恢复该状态。 下面的示例演示了该方法。
在应用中创建状态容器类,该类具有用于保存应用的状态值的属性。 在以下示例中,容器用于维护默认 Blazor 项目模板的 Counter
组件 (Pages/Counter.razor
) 的计数器值。 用于序列化和反序列化容器的方法基于 System.Text.Json。
using System.Text.Json;
public class StateContainer
public int CounterValue { get; set; }
public string GetStateForLocalStorage()
return JsonSerializer.Serialize(this);
public void SetStateFromLocalStorage(string locallyStoredState)
var deserializedState =
JsonSerializer.Deserialize<StateContainer>(locallyStoredState);
CounterValue = deserializedState.CounterValue;
Counter
组件使用状态容器来维护组件以外的 currentCount
值:
@page "/counter"
@inject StateContainer State
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
protected override void OnInitialized()
if (State.CounterValue > 0)
currentCount = State.CounterValue;
private void IncrementCount()
currentCount++;
State.CounterValue = currentCount;
从 RemoteAuthenticationState 创建 ApplicationAuthenticationState
。 提供 Id
属性,该属性用作本地存储状态的标识符。
ApplicationAuthenticationState.cs
:
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public class ApplicationAuthenticationState : RemoteAuthenticationState
public string? Id { get; set; }
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public class ApplicationAuthenticationState : RemoteAuthenticationState
public string Id { get; set; }
Authentication
组件 (Pages/Authentication.razor
) 使用本地会话存储以及 StateContainer
序列化和反序列化方法(GetStateForLocalStorage
和 SetStateFromLocalStorage
)保存和还原应用的状态:
@page "/authentication/{action}"
@inject IJSRuntime JS
@inject StateContainer State
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorViewCore Action="@Action"
TAuthenticationState="ApplicationAuthenticationState"
AuthenticationState="AuthenticationState"
OnLogInSucceeded="RestoreState"
OnLogOutSucceeded="RestoreState" />
@code {
[Parameter]
public string? Action { get; set; }
public ApplicationAuthenticationState AuthenticationState { get; set; } =
new ApplicationAuthenticationState();
protected override async Task OnInitializedAsync()
if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
Action) ||
RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
Action))
AuthenticationState.Id = Guid.NewGuid().ToString();
await JS.InvokeVoidAsync("sessionStorage.setItem",
AuthenticationState.Id, State.GetStateForLocalStorage());
private async Task RestoreState(ApplicationAuthenticationState state)
if (state.Id != null)
var locallyStoredState = await JS.InvokeAsync<string>(
"sessionStorage.getItem", state.Id);
if (locallyStoredState != null)
State.SetStateFromLocalStorage(locallyStoredState);
await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
@page "/authentication/{action}"
@inject IJSRuntime JS
@inject StateContainer State
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorViewCore Action="@Action"
TAuthenticationState="ApplicationAuthenticationState"
AuthenticationState="AuthenticationState"
OnLogInSucceeded="RestoreState"
OnLogOutSucceeded="RestoreState" />
@code {
[Parameter]
public string Action { get; set; }
public ApplicationAuthenticationState AuthenticationState { get; set; } =
new ApplicationAuthenticationState();
protected override async Task OnInitializedAsync()
if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
Action) ||
RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
Action))
AuthenticationState.Id = Guid.NewGuid().ToString();
await JS.InvokeVoidAsync("sessionStorage.setItem",
AuthenticationState.Id, State.GetStateForLocalStorage());
private async Task RestoreState(ApplicationAuthenticationState state)
if (state.Id != null)
var locallyStoredState = await JS.InvokeAsync<string>(
"sessionStorage.getItem", state.Id);
if (locallyStoredState != null)
State.SetStateFromLocalStorage(locallyStoredState);
await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
本示例使用 Azure Active Directory (AAD) 进行身份验证。 在 Program.cs
中:
ApplicationAuthenticationState
配置为 Microsoft 身份验证库 (MSAL) RemoteAuthenticationState
类型。
在服务容器中注册状态容器。
builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>(options =>
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
builder.Services.AddSingleton<StateContainer>();
自定义应用路由
默认情况下,Microsoft.AspNetCore.Components.WebAssembly.Authentication
库使用下表中显示的路由表示不同的身份验证状态。
上表中显示的路由可通过 RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.AuthenticationPaths 进行配置。 设置选项以提供自定义路由时,确认应用具有可处理每个路径的路由。
在以下示例中,所有路径都带有 /security
前缀。
Authentication
组件 (Pages/Authentication.razor
):
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />
@code{
[Parameter]
public string? Action { get; set; }
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />
@code{
[Parameter]
public string Action { get; set; }
在 Program.cs
中:
builder.Services.AddApiAuthorization(options => {
options.AuthenticationPaths.LogInPath = "security/login";
options.AuthenticationPaths.LogInCallbackPath = "security/login-callback";
options.AuthenticationPaths.LogInFailedPath = "security/login-failed";
options.AuthenticationPaths.LogOutPath = "security/logout";
options.AuthenticationPaths.LogOutCallbackPath = "security/logout-callback";
options.AuthenticationPaths.LogOutFailedPath = "security/logout-failed";
options.AuthenticationPaths.LogOutSucceededPath = "security/logged-out";
options.AuthenticationPaths.ProfilePath = "security/profile";
options.AuthenticationPaths.RegisterPath = "security/register";
如果需求要求使用完全不同的路径,请按照前面所述设置路由,并使用显式的操作参数呈现 RemoteAuthenticatorView:
@page "/register"
<RemoteAuthenticatorView Action="@RemoteAuthenticationActions.Register" />
如果选择这样做,则允许将 UI 分成不同的页面。
自定义身份验证用户界面
RemoteAuthenticatorView 包括每个身份验证状态的一组默认 UI 片段。 可以通过传入自定义 RenderFragment 自定义每个状态。 要在初始登录过程中自定义显示的文本,可以按如下所示更改 RemoteAuthenticatorView。
Authentication
组件 (Pages/Authentication.razor
):
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action">
<LoggingIn>
You are about to be redirected to https://login.microsoftonline.com.
</LoggingIn>
</RemoteAuthenticatorView>
@code{
[Parameter]
public string? Action { get; set; }
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action">
<LoggingIn>
You are about to be redirected to https://login.microsoftonline.com.
</LoggingIn>
</RemoteAuthenticatorView>
@code{
[Parameter]
public string Action { get; set; }
下表中显示了 RemoteAuthenticatorView 的一个片段,该片段可用于每个身份验证路由。
Fragment
可自定义绑定到应用的用户。
使用有效负载声明自定义用户
在以下示例中,所有经过应用身份验证的用户都会收到每种用户身份验证方法的 amr
声明。 amr
声明确定在 Microsoft Identity Platform v1.0 有效负载声明中如何对令牌主体进行身份验证。 该示例使用基于 RemoteUserAccount 的自定义用户帐户类。
创建扩展 RemoteUserAccount 类的类。 下面的示例将 AuthenticationMethod
属性设置为用户的 amr
JSON 属性值数组。 对用户进行身份验证时,由框架自动填充 AuthenticationMethod
。
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public class CustomUserAccount : RemoteUserAccount
[JsonPropertyName("amr")]
public string[]? AuthenticationMethod { get; set; }
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public class CustomUserAccount : RemoteUserAccount
[JsonPropertyName("amr")]
public string[] AuthenticationMethod { get; set; }
创建一个扩展 AccountClaimsPrincipalFactory<TAccount> 的工厂,根据存储在 CustomUserAccount.AuthenticationMethod
中的用户身份验证方法创建声明:
using System.Security.Claims;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
public class CustomAccountFactory
: AccountClaimsPrincipalFactory<CustomUserAccount>
public CustomAccountFactory(NavigationManager navigation,
IAccessTokenProviderAccessor accessor) : base(accessor)
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
CustomUserAccount account, RemoteAuthenticationUserOptions options)
var initialUser = await base.CreateUserAsync(account, options);
if (initialUser.Identity != null && initialUser.Identity.IsAuthenticated)
var userIdentity = (ClaimsIdentity)initialUser.Identity;
if (account.AuthenticationMethod is not null)
foreach (var value in account.AuthenticationMethod)
userIdentity.AddClaim(new Claim("amr", value));
return initialUser;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
public class CustomAccountFactory
: AccountClaimsPrincipalFactory<CustomUserAccount>
public CustomAccountFactory(NavigationManager navigation,
IAccessTokenProviderAccessor accessor) : base(accessor)
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
CustomUserAccount account, RemoteAuthenticationUserOptions options)
var initialUser = await base.CreateUserAsync(account, options);
if (initialUser.Identity != null && initialUser.Identity.IsAuthenticated)
var userIdentity = (ClaimsIdentity)initialUser.Identity;
foreach (var value in account.AuthenticationMethod)
userIdentity.AddClaim(new Claim("amr", value));
return initialUser;
为正在使用的验证提供程序注册 CustomAccountFactory
。 以下任何注册均有效:
AddOidcAuthentication:
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
builder.Services.AddOidcAuthentication<RemoteAuthenticationState,
CustomUserAccount>(options =>
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount, CustomAccountFactory>();
AddMsalAuthentication:
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
CustomUserAccount>(options =>
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount, CustomAccountFactory>();
AddApiAuthorization:
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
builder.Services.AddApiAuthorization<RemoteAuthenticationState,
CustomUserAccount>(options =>
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount, CustomAccountFactory>();
具有自定义用户帐户类的 AAD 安全组和角色
有关使用 AAD 安全组、AAD 管理员角色和自定义用户帐户类的其他示例,请参阅 ASP.NET Core Blazor WebAssembly 与 Azure Active Directory 组和角色。
使用身份验证预呈现
目前不支持预呈现需要身份验证和授权的内容。 遵循任一 Blazor WebAssembly 安全应用主题中的指导后,请按照以下说明创建应用:
预呈现不需要授权的路径。
不预呈现需要授权的路径。
对于 Client 项目的 Program.cs
文件,将常见服务注册纳入单独的方法(例如在 Client 项目中创建 ConfigureCommonServices
方法)。 常见服务是开发人员注册以供客户端和服务器项目使用的服务。
public static void ConfigureCommonServices(IServiceCollection services)
services.Add...;
Program.cs
:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddScoped( ... );
ConfigureCommonServices(builder.Services);
await builder.Build().RunAsync();
在 Server 项目的 Program.cs
文件中,注册以下附加服务并调用 ConfigureCommonServices
:
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
builder.Services.AddRazorPages();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ServerAuthenticationStateProvider>();
Client.Program.ConfigureCommonServices(services);
在 Server 项目的 Startup.ConfigureServices
方法中,注册以下附加服务并调用 ConfigureCommonServices
:
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
public void ConfigureServices(IServiceCollection services)
services.AddRazorPages();
services.AddScoped<AuthenticationStateProvider,
ServerAuthenticationStateProvider>();
services.AddScoped<SignOutSessionStateManager>();
Client.Program.ConfigureCommonServices(services);
有关Blazor 框架服务器身份验证提供程序 (ServerAuthenticationStateProvider
) 的详细信息,请参阅 ASP.NET Core Blazor 身份验证和授权。
在 Server 项目的 Pages/_Host.cshtml
文件中,将 Component
标记帮助程序 (<component ... />
) 替换为以下内容:
<div id="app">
@if (HttpContext.Request.Path.StartsWithSegments("/authentication"))
<component type="typeof({CLIENT APP ASSEMBLY NAME}.App)"
render-mode="WebAssembly" />
<component type="typeof({CLIENT APP ASSEMBLY NAME}.App)"
render-mode="WebAssemblyPrerendered" />
在上面的示例中:
占位符 {CLIENT APP ASSEMBLY NAME}
是客户端应用的程序集名称(例如 BlazorSample.Client
)。
/authentication
路径段的条件检查:
- 避免了身份验证路径的预呈现 (
render-mode="WebAssembly"
)。
- 预呈现了非身份验证路径的 (
render-mode="WebAssemblyPrerendered"
)。
用于托管应用和第三方登录提供程序的选项
使用第三方提供程序对托管的 Blazor WebAssembly 应用进行身份验证和授权时,有几个选项可用于对用户进行身份验证。 选择哪一种选项取决于方案。
有关详细信息,请参阅在 ASP.NET Core 中保留来自外部提供程序的附加声明和令牌。
对用户进行身份验证,以仅调用受保护的第三方 API
使用针对第三方 API 提供程序的客户端 OAuth 流对用户进行身份验证:
builder.services.AddOidcAuthentication(options => { ... });
在本方案中:
托管应用的服务器不会发挥作用。
无法保护服务器上的 API。
应用只能调用受保护的第三方 API。
使用第三方提供程序对用户进行身份验证,并在主机服务器和第三方调用受保护的 API
使用第三方登录提供程序配置 Identity。 获取第三方 API 访问所需的令牌并进行存储。
当用户登录时,Identity 将在身份验证过程中收集访问令牌和刷新令牌。 此时,可通过几种方法向第三方 API 进行 API 调用。
使用服务器访问令牌检索第三方访问令牌
使用服务器上生成的访问令牌从服务器 API 终结点检索第三方访问令牌。 在此处,使用第三方访问令牌直接从客户端上的 Identity 调用第三方 API 资源。
不建议使用此方法。此方法需要将第三方访问令牌视为针对公共客户端生成。 在 OAuth 范畴,公共应用没有客户端机密,因为不能信任此类应用可以安全地存储机密,将为机密客户端生成访问令牌。 机密客户端具有客户端机密,并且假定能够安全地存储机密。
第三方访问令牌可能会被授予其他作用域,以便基于第三方为更受信任的客户端发出令牌的情况执行敏感操作。
同样,不应向不受信任的客户端颁发刷新令牌,因为这样做会给客户端提供无限制的访问权限,除非存在其他限制。
从客户端向服务器 API 发出 API 调用以便调用第三方 API
从客户端向服务器 API 发出 API 调用。 从服务器中检索第三方 API 资源的访问令牌,并发出任何所需调用。
建议使用此方法。尽管此方法需要额外的网络跃点通过服务器来调用第三方 API,但最终可提供更安全的体验:
服务器可以存储刷新令牌,并确保应用不会失去对第三方资源的访问权限。
应用无法从服务器泄漏可能包含更多敏感权限的访问令牌。
使用 OpenID Connect (OIDC) v2.0 终结点
身份验证库和 Blazor 项目模板使用 Open ID Connect (OIDC) v1.0 终结点。 要使用 v2.0 终结点,请配置 JWT 持有者 JwtBearerOptions.Authority 选项。 在下面的示例中,通过向 Authority 属性追加 v2.0
段,将 AAD 配置为 v2.0:
using Microsoft.AspNetCore.Authentication.JwtBearer;
builder.Services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme,
options =>
options.Authority += "/v2.0";
也可以在应用设置 (appsettings.json
) 文件中进行设置:
"Local": {
"Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
如果将段添加到授权不适合应用的 OIDC 提供程序(例如,使用非 AAD 提供程序),则直接设置 Authority 属性。 使用 Authority
键在 JwtBearerOptions 或应用设置文件 (appsettings.json
) 中设置属性。
ID 令牌中的声明列表针对 v2.0 终结点会发生更改。 有关详细信息,请参阅为什么要更新到 Microsoft 标识平台 (v2.0)?。
若要将 Blazor WebAssembly 应用配置为使用 ASP.NET Core gRPC 框架:
在服务器上启用 gRPC-Web。 有关详细信息,请参阅 ASP.NET Core gRPC 应用中的 gRPC-Web。
为应用的消息处理程序注册 gRPC 服务。 下面的示例将应用的授权消息处理程序配置为使用 gRPC 教程中的 GreeterClient
服务 (Program.cs
):
using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using {ASSEMBLY NAME}.Shared;
builder.Services.AddScoped(sp =>
var baseAddressMessageHandler =
sp.GetRequiredService<BaseAddressAuthorizationMessageHandler>();
baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
var grpcWebHandler =
new GrpcWebHandler(GrpcWebMode.GrpcWeb, baseAddressMessageHandler);
var channel = GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress,
new GrpcChannelOptions { HttpHandler = grpcWebHandler });
return new Greeter.GreeterClient(channel);
占位符 {ASSEMBLY NAME}
是应用的程序集名称(例如 BlazorSample
)。 将 .proto
文件放在托管的 Blazor 解决方案的 Shared
项目中。
客户端应用中的组件可使用 gRPC 客户端 (Pages/Grpc.razor
) 发出 gRPC 调用:
@page "/grpc"
@using Microsoft.AspNetCore.Authorization
@using {ASSEMBLY NAME}.Shared
@attribute [Authorize]
@inject Greeter.GreeterClient GreeterClient
<h1>Invoke gRPC service</h1>
<input @bind="name" placeholder="Type your name" />
<button @onclick="GetGreeting" class="btn btn-primary">Call gRPC service</button>
Server response: <strong>@serverResponse</strong>
@code {
private string name = "Bert";
private string? serverResponse;
private async Task GetGreeting()
var request = new HelloRequest { Name = name };
var reply = await GreeterClient.SayHelloAsync(request);
serverResponse = reply.Message;
catch (Grpc.Core.RpcException ex)
when (ex.Status.DebugException is
AccessTokenNotAvailableException tokenEx)
tokenEx.Redirect();
@page "/grpc"
@using Microsoft.AspNetCore.Authorization
@using {ASSEMBLY NAME}.Shared
@attribute [Authorize]
@inject Greeter.GreeterClient GreeterClient
<h1>Invoke gRPC service</h1>
<input @bind="name" placeholder="Type your name" />
<button @onclick="GetGreeting" class="btn btn-primary">Call gRPC service</button>
Server response: <strong>@serverResponse</strong>
@code {
private string name = "Bert";
private string serverResponse;
private async Task GetGreeting()
var request = new HelloRequest { Name = name };
var reply = await GreeterClient.SayHelloAsync(request);
serverResponse = reply.Message;
catch (Grpc.Core.RpcException ex)
when (ex.Status.DebugException is
AccessTokenNotAvailableException tokenEx)
tokenEx.Redirect();
占位符 {ASSEMBLY NAME}
是应用的程序集名称(例如 BlazorSample
)。 若要使用 Status.DebugException
属性,请使用 Grpc.Net.Client
版本 2.30.0 或更高版本。
有关详细信息,请参阅 ASP.NET Core gRPC 应用中的 gRPC-Web。
替换 AuthenticationService
实现
下面的小节说明了如何替换:
任何 JavaScript AuthenticationService
实现。
适用于 JavaScript 的 Microsoft 身份验证库 (MSAL.js
)。
替换任何 JavaScript AuthenticationService
实现
创建一个 JavaScript 库来处理自定义身份验证详细信息。
本部分的指南是默认 RemoteAuthenticationService<TRemoteAuthenticationState,TAccount,TProviderOptions> 的实现详细信息。 本部分的 TypeScript 代码仅适用于 ASP.NET Core 7.0,在即将发布的 ASP.NET Core 版本中可能会发生更改,恕不另行通知。
// .NET makes calls to an AuthenticationService object in the Window.
declare global {
interface Window { AuthenticationService: AuthenticationService }
export interface AuthenticationService {
// Init is called to initialize the AuthenticationService.
public static init(settings: UserManagerSettings & AuthorizeServiceSettings, logger: any) : Promise<void>;
// Gets the currently authenticated user.
public static getUser() : Promise<{[key: string] : string }>;
// Tries to get an access token silently.
public static getAccessToken(options: AccessTokenRequestOptions) : Promise<AccessTokenResult>;
// Tries to sign in the user or get an access token interactively.
public static signIn(context: AuthenticationContext) : Promise<AuthenticationResult>;
// Handles the sign-in process when a redirect is used.
public static async completeSignIn(url: string) : Promise<AuthenticationResult>;
// Signs the user out.
public static signOut(context: AuthenticationContext) : Promise<AuthenticationResult>;
// Handles the signout callback when a redirect is used.
public static async completeSignOut(url: string) : Promise<AuthenticationResult>;
// The rest of these interfaces match their C# definitions.
export interface AccessTokenRequestOptions {
scopes: string[];
returnUrl: string;
export interface AccessTokenResult {
status: AccessTokenResultStatus;
token?: AccessToken;
export interface AccessToken {
value: string;
expires: Date;
grantedScopes: string[];
export enum AccessTokenResultStatus {
Success = 'Success',
RequiresRedirect = 'RequiresRedirect'
export enum AuthenticationResultStatus {
Redirect = 'Redirect',
Success = 'Success',
Failure = 'Failure',
OperationCompleted = 'OperationCompleted'
export interface AuthenticationResult {
status: AuthenticationResultStatus;
state?: unknown;
message?: string;
export interface AuthenticationContext {
state?: unknown;
interactiveRequest: InteractiveAuthenticationRequest;
export interface InteractiveAuthenticationRequest {
scopes?: string[];
additionalRequestParameters?: { [key: string]: any };
可以通过删除原始 <script>
标记并添加用于加载自定义库的 <script>
标记来导入库。 以下示例演示了如何将默认的 <script>
标记替换为从 wwwroot/js
文件夹中加载 CustomAuthenticationService.js
库的标记。
在 </body>
结束标记内的 Blazor 脚本 (_framework/blazor.webassembly.js
) 前的 wwwroot/index.html
中:
- <script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script>
+ <script src="js/CustomAuthenticationService.js"></script>
有关详细信息,请参阅 dotnet/aspnetcore
GitHub 存储库中的 AuthenticationService.ts
。
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
替换适用于 JavaScript 的 Microsoft 身份验证库 (MSAL.js
)
如果应用需要适用于 JavaScript 的 Microsoft 身份验证库 (MSAL.js
) 的自定义版本,请执行以下步骤:
确认系统具有最新的开发人员 .NET SDK 或从 .NET Core SDK:安装程序和二进制文件获取并安装最新的开发人员 SDK。 此方案不需要配置内部 NuGet 源。
设置 dotnet/aspnetcore
GitHub 存储库,以按照从源生成 ASP.NET Core 中的文档进行开发。 分叉和克隆或下载 dotnet/aspnetcore
GitHub 存储库的 ZIP 存档。
打开 src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json
文件,并设置所需的 @azure/msal-browser
版本。 有关已发布版本列表,请访问 @azure/msal-browser
npm 网站,然后选择“版本”选项卡。
使用命令行界面中的 yarn build
命令在 src/Components/WebAssembly/Authentication.Msal/src
文件夹中生成 Authentication.Msal
项目。
如果应用使用压缩资产 (Brotli/Gzip),则压缩 Interop/dist/Release/AuthenticationService.js
文件。
如果生成了文件,请将该文件的 AuthenticationService.js
文件和压缩版本 (.br
/.gz
) 从 Interop/dist/Release
文件夹复制到应用的已发布资产中应用的 publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal
文件夹中。
传递自定义提供程序选项
定义一个用于将数据传递到基础 JavaScript 库的类。
类的结构必须与使用 System.Text.Json 序列化 JSON 时库预期的结构一致。
以下示例演示了一个 ProviderOptions
类,它的 JsonPropertyName
属性与假设的自定义提供程序库的预期一致:
public class ProviderOptions
public string? Authority { get; set; }
public string? MetadataUrl { get; set; }
[JsonPropertyName("client_id")]
public string? ClientId { get; set; }
public IList<string> DefaultScopes { get; } =
new List<string> { "openid", "profile" };
[JsonPropertyName("redirect_uri")]
public string? RedirectUri { get; set; }
[JsonPropertyName("post_logout_redirect_uri")]
public string? PostLogoutRedirectUri { get; set; }
[JsonPropertyName("response_type")]
public string? ResponseType { get; set; }
[JsonPropertyName("response_mode")]
public string? ResponseMode { get; set; }
public class ProviderOptions
public string Authority { get; set; }
public string MetadataUrl { get; set; }
[JsonPropertyName("client_id")]
public string ClientId { get; set; }
public IList<string> DefaultScopes { get; } =
new List<string> { "openid", "profile" };
[JsonPropertyName("redirect_uri")]
public string RedirectUri { get; set; }
[JsonPropertyName("post_logout_redirect_uri")]
public string PostLogoutRedirectUri { get; set; }
[JsonPropertyName("response_type")]
public string ResponseType { get; set; }
[JsonPropertyName("response_mode")]
public string ResponseMode { get; set; }
在 DI 系统内注册提供程序选项并配置相应的值:
builder.Services.AddRemoteAuthentication<RemoteAuthenticationState, RemoteUserAccount,
ProviderOptions>(options => {
options.Authority = "...";
options.MetadataUrl = "...";
options.ClientId = "...";
options.DefaultScopes = new List<string> { "openid", "profile", "myApi" };
options.RedirectUri = "https://localhost:5001/authentication/login-callback";
options.PostLogoutRedirectUri = "https://localhost:5001/authentication/logout-callback";
options.ResponseType = "...";
options.ResponseMode = "...";
前面的示例使用常规字符串字面量设置重定向 URI。 以下是可用的替代方法:
使用 IWebAssemblyHostEnvironment.BaseAddress 的 TryCreate:
Uri.TryCreate(
$"{builder.HostEnvironment.BaseAddress}authentication/login-callback",
UriKind.Absolute, out var redirectUri);
options.RedirectUri = redirectUri;
主机生成器配置:
options.RedirectUri = builder.Configuration["RedirectUri"];
wwwroot/appsettings.json
:
"RedirectUri": "https://localhost:5001/authentication/login-callback"
将 Graph API 和 ASP.NET Core 结合使用Blazor WebAssembly
具有提取 API 请求选项的 HttpClient
和 HttpRequestMessage