.NET6 Blazor Server接入AAD后返回的access token为null问题排查
.NET6 Blazor Server对接Azure AD时Access Token返回null问题修复
问题背景
该问题在技术社区有较多公开讨论,但现有公开方案无法覆盖当前遇到的场景。
当前运行环境为基于.NET6的Blazor Server-Side应用,已完成Azure Active Directory对接,可正常走完AAD身份认证流程,拿到有效的ClaimsPrincipal实例。
初始Program.cs中AAD相关配置片段如下:
[...] builder.Services .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) ; builder.Services .Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options => { options.SaveTokens = true; }); [...]
配置文件中AzureAd节点内容如下:
[...] "AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "XXX.com", "TenantId": "5dXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "ClientId": "26XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "CallbackPath": "/signin-oidc" } [...]
按照公开方案的描述,完成身份认证后在_Host.cshtml调用取token方法本应拿到有效access token,但实际获取到的token值为null,异常状态如下:
此前已尝试按照公开方案追加EnableTokenAcquisitionToCallDownstreamApi、AddInMemoryTokenCaches相关调用,调整后的配置如下:
[...] builder.Services .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi(new string[] { "openid" }) .AddInMemoryTokenCaches() ;
但调整后问题没有改善,access token依旧返回null。
问题根因
该问题由三个核心错误导致:
- Scope配置错误:
EnableTokenAcquisitionToCallDownstreamApi传入的scope仅为openid,这是OIDC协议的基础认证scope,仅会触发AAD返回用于站点身份核验的id_token,不会返回可用于访问下游资源的access token。 - Token获取方式错误:Blazor Server基于长连接Circuit模型运行,Circuit建立后HttpContext不会持续可用,仅配置
SaveTokens=true只会把token存在认证Cookie中,不会自动注入到Blazor运行时的DI容器,在组件生命周期内直接通过HttpContext取token大概率拿到null。 - 云端配置缺失:如果Azure AD应用注册的身份验证配置中没有显式勾选允许返回访问令牌,即使服务端配置了正确的scope,AAD的认证回调响应中也不会携带access_token字段。
修复步骤
- 修正服务端DI配置,移除单独写的
OpenIdConnectOptions.SaveTokens配置,使用Microsoft Identity内置的token管理流程,替换EnableTokenAcquisitionToCallDownstreamApi中的scope为实际需要访问的资源scope(不要只传openid,如果要访问Microsoft Graph就传对应的Graph权限scope,如果要访问自有受保护API就传API暴露的自定义scope),正确配置示例:
builder.Services .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) // 替换为实际业务需要的资源scope,示例为访问Graph用户信息的scope .EnableTokenAcquisitionToCallDownstreamApi(new []{ "User.Read" }) .AddInMemoryTokenCaches();
- 替换token获取方式,不要在Blazor组件/服务中直接依赖HttpContext取token,而是注入框架提供的
ITokenAcquisition服务获取有效token,示例:
@inject ITokenAcquisition TokenAcquisition @code { private string? _accessToken; protected override async Task OnInitializedAsync() { // 传入和配置中一致的scope获取对应资源的access token _accessToken = await TokenAcquisition.GetAccessTokenForUserAsync(new []{ "User.Read" }); // 后续可直接用该token调用下游API } }
- 如果确实需要在_Host.cshtml页面初始化阶段取token,必须在Blazor应用渲染前从当前请求的HttpContext中获取,再通过组件参数传递到Blazor应用内部,示例:
在_Host.cshtml的代码块中取token:
@{ // 该逻辑仅在首次页面请求时执行有效 var accessToken = await HttpContext.GetTokenAsync("access_token"); }
将token作为参数传入根组件:
<component type="typeof(App)" render-mode="ServerPrerendered" param-InitialAccessToken="accessToken" />
注意:如果完成上述配置后仍然拿不到token,需要登录Azure Portal检查对应应用注册的配置:在「身份验证」-「Web平台配置」板块,确认已经勾选访问令牌和ID令牌两个选项,保存配置后重试即可。
内容的提问来源于stack exchange,提问作者Kzryzstof
相关产品推荐
相关产品推荐

