无法让Blazor Server应用通过Azure AD B2C调用受保护的下游Web API
问题描述
使用dotnet new创建Blazor Server项目后,可通过AAD B2C用户流正常登录,但点击“Call Web API”按钮时,在CallWebAPI.razor的await downstreamAPI.CallWebApiForUserAsync处抛出异常。
创建项目的命令
dotnet new blazorserver --output "PlayingWithBlazor" --framework net6.0 --auth IndividualB2C --aad-b2c-instance "https://{mytenant}.b2clogin.com/" --domain "{mydomain}.onmicrosoft.com" --client-id "{myClientID}" --susi-policy-id "B2C_1_SignUp" --called-api-url "https://localhost:7042/api" --called-api-scopes "https://{mytennant}.onmicrosoft.com/a82e00f8-939d-47ab-b2f3-4e557020f729/access_as_user"
CallWebApi.razor的@Code代码块
@code { private HttpResponseMessage? response; private string? apiResult; protected override async Task OnInitializedAsync() { try { response = await downstreamAPI.CallWebApiForUserAsync( "DownstreamApi", options => options.RelativePath = "/Subscriber"); if (response.StatusCode == System.Net.HttpStatusCode.OK) { apiResult = await response.Content.ReadAsStringAsync(); } else { apiResult = "Failed to call the web API"; } } catch (Exception ex) { ConsentHandler.HandleException(ex); } }
异常信息
Microsoft.Identity.Web.MicrosoftIdentityWebChallengeUserException HResult=0x80131500 Message=IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. Source=Microsoft.Identity.Web StackTrace: at Microsoft.Identity.Web.TokenAcquisition.<GetAuthenticationResultForUserAsync>d__16.MoveNext() at Microsoft.Identity.Web.DownstreamWebApi.<CallWebApiForUserAsync>d__5.MoveNext() at PlayingWithBlazor.Pages.CallWebApi.<OnInitializedAsync>d__3.MoveNext() in C:\Users\AndySchneider\source\repos\PlayingWithBlazor\Pages\CallWebApi.razor:line 33 This exception was originally thrown at this call stack: [External Code] Inner Exception 1: MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.
Web API代码
// GET: api/<SubscriberController> [Authorize] [HttpGet] public IEnumerable<Subscriber> Get() { return subscriberRepository.GetAllAsync().Result; }
Program.cs代码
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using PlayingWithBlazor.Data; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Identity.Client; var builder = WebApplication.CreateBuilder(args); // Add services to the container. var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")) .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) .AddInMemoryTokenCaches(); builder.Services.AddControllersWithViews() .AddMicrosoftIdentityUI(); builder.Services.AddAuthorization(options => { // By default, all incoming requests will be authorized according to the default policy options.FallbackPolicy = options.DefaultPolicy; }); builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor() .AddMicrosoftIdentityConsentHandler(); builder.Services.AddSingleton<WeatherForecastService>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapBlazorHub(); app.MapFallbackToPage("/_Host"); app.Run();
解决方案
异常核心原因是调用AcquireTokenSilent时未传入账户或登录提示信息,以下是具体修复步骤:
1. 传递用户上下文到API调用
在Blazor Server中需确保用户身份上下文被正确传递,修改CallWebApi.razor:
// 注入依赖 @inject IHttpContextAccessor HttpContextAccessor @inject NavigationManager NavigationManager @code { private HttpResponseMessage? response; private string? apiResult; protected override async Task OnInitializedAsync() { try { // 显式传递当前用户 response = await downstreamAPI.CallWebApiForUserAsync( "DownstreamApi", options => options.RelativePath = "/Subscriber", user: HttpContextAccessor.HttpContext?.User); if (response.StatusCode == System.Net.HttpStatusCode.OK) { apiResult = await response.Content.ReadAsStringAsync(); } else { apiResult = "调用Web API失败"; } } catch (MicrosoftIdentityWebChallengeUserException) { // 触发重新登录以获取令牌 NavigationManager.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}"); } catch (Exception ex) { apiResult = $"调用失败: {ex.Message}"; } }
同时在Program.cs中注册IHttpContextAccessor:
builder.Services.AddHttpContextAccessor();
2. 验证配置文件与权限
- 检查
appsettings.json中DownstreamApi:Scopes是否与创建项目时指定的作用域一致,注意修正创建命令中拼写错误的{mytennant}为{mytenant}。 - 在Azure AD B2C控制台确认Blazor应用已被授予目标Web API的访问权限,且用户已完成权限同意。
3. 优化令牌缓存与异常处理
当前使用的AddInMemoryTokenCaches()适用于开发环境,生产环境可考虑使用分布式缓存。同时扩展异常处理逻辑,确保用户能被引导至登录流程获取所需令牌。
内容的提问来源于stack exchange,提问作者Andy Schneider
相关产品推荐
相关产品推荐

