Blazor在OnTokenValidated事件调用受保护API遇Token获取异常
解决Blazor Azure AD B2C在OnTokenValidated事件中调用下游API的MsalUiRequiredException错误
问题原因
在OnTokenValidated事件触发时,Microsoft Identity Web尚未将用户账户信息持久化到TokenCache中,此时调用GetAccessTokenForUserAsync无法定位到用户账户,从而抛出user_null错误。另外,代码中用Task.Run().GetAwaiter().GetResult()同步阻塞异步方法,会导致请求上下文丢失,进一步加重问题。
解决方案
1. 手动构造用户账户信息传入Token获取方法
从TokenValidatedContext的Principal中提取用户对象ID(oid)和租户ID(tid),手动创建IAccount实例,传递给GetAccessTokenForUserAsync重载方法,确保能匹配到对应的用户账户。
2. 全程保持异步调用
移除同步阻塞代码,维持异步执行流程,避免上下文丢失。
3. (可选)将API调用逻辑移至后续阶段
如果业务允许,可把下游API调用逻辑放到用户登录完成后的中间件、页面初始化方法(如OnInitializedAsync)中,此时用户上下文已完全建立,无需手动处理账户信息。
修正后的代码
Program.cs(保持原有配置不变)
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(options => { builder.Configuration.GetSection("AzureAd").Bind(options); var prevOnTokenValidatedHandler = options.Events.OnTokenValidated; options.Events.OnTokenValidated = async context => { await prevOnTokenValidatedHandler(context).ConfigureAwait(false); await AuthorizationHelper.ValidateAADAppToken(context); }; } ) .EnableTokenAcquisitionToCallDownstreamApi() .AddInMemoryTokenCaches();
AuthorizationHelper.cs(修正后的逻辑)
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Identity.Web; using Microsoft.Identity.Client; using System.Security.Claims; internal static class AuthorizationHelper { internal static async Task ValidateAADAppToken(TokenValidatedContext context) { var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); var scopes = new[] { "https://<Tenant>/<ApiId>/access_as_user" }; // 从当前Principal提取用户核心标识 var oid = context.Principal.FindFirstValue(ClaimConstants.ObjectId); var tid = context.Principal.FindFirstValue(ClaimConstants.TenantId); var upn = context.Principal.FindFirstValue(ClaimConstants.Upn); // 手动构造用户账户实例 var account = new Account(oid, tid, upn); // 异步获取AccessToken,传入手动构造的账户 var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes, account: account); // 调用下游API using var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken); var response = await httpClient.GetAsync("https://localhost:7297/WeatherForecast"); // 示例:处理API返回的权限信息,添加到用户Claims中 if (response.IsSuccessStatusCode) { var userPermissions = await response.Content.ReadFromJsonAsync<UserPermissions>(); var claimsIdentity = context.Principal.Identity as ClaimsIdentity; claimsIdentity?.AddClaims(userPermissions.Roles.Select(r => new Claim(ClaimTypes.Role, r))); claimsIdentity?.AddClaims(userPermissions.Groups.Select(g => new Claim("groups", g))); } } // 匹配API返回结构的模型类 private class UserPermissions { public List<string> Roles { get; set; } = new(); public List<string> Groups { get; set; } = new(); // 可添加其他权限字段 } }
额外注意事项
- 确保Azure AD B2C应用注册中已配置好下游API的权限,且用户已被授予对应权限。
- 若使用Microsoft.Identity.Web最新版本,部分API可能有细微调整,请根据实际版本适配代码。
内容的提问来源于stack exchange,提问作者Markus
相关产品推荐
相关产品推荐

