调用GetAuthenticationResultForAppAsync时AccessToken有效但IdToken为空的问题
问题描述
尝试使用Microsoft.Identity.Web实现AzureAD认证,调用GetAuthenticationResultForAppAsync返回的结果中AccessToken有值但IdToken为null,导致令牌验证时出现空指针异常。
代码片段
services.AddOpenIdConnect(options => { var x = Configuration["AzureAd:CallbackPath"]; options.ClientId = Configuration["AzureAd:ClientId"]; options.ClientSecret = Configuration["AzureAd:ClientSecret"]; options.CallbackPath = new PathString(Configuration["AzureAd:CallbackPath"]); options.Authority = Configuration["AzureAd:Instance"] + Configuration["AzureAd:TenantId"]; options.ResponseType = "code id_token"; options.Authority = options.Authority + "/v2.0/"; //options.Scope.Clear(); //options.Scope.Add("User.ReadBasic.All"); options.Scope.Add("user.read"); options.RequireHttpsMetadata = false; // 开发环境配置 options.Events = new OpenIdConnectEvents { OnTokenValidated = OnTokenValidated }; // 由MSAL.NET处理授权码兑换,使令牌可在令牌缓存中供后续控制器使用 var handler = options.Events.OnAuthorizationCodeReceived; options.Events.OnAuthorizationCodeReceived = async context => { context.HandleCodeRedemption(); var _tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); //await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, options.Scope); var result = await _tokenAcquisition.GetAuthenticationResultForAppAsync("https://americancancer.onmicrosoft.com/xxxx/.default"); //var result = await _tokenAcquisition.GetAuthenticationResultForUserAsync(options.Scope); // 不要将AccessToken共享给ASP.NET Core,否则ASP.NET会缓存它,后续增量授权时无法发送OAuth2.0请求 // 共享ID Token以让应用知晓用户身份(在HttpContext.User中) context.HandleCodeRedemption(null, result.IdToken); await handler(context); }; }); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(Configuration, "AzureAd") .EnableTokenAcquisitionToCallDownstreamApi() .AddInMemoryTokenCaches();
错误信息
Microsoft.AspNetCore.Authentication.AuthenticationFailureException: 处理远程登录时遇到错误。
---> System.ArgumentNullException: IDX10000: 参数'token'不能为null或空对象。(参数'token')
at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.ValidateTokenUsingHandlerAsync(String idToken, AuthenticationProperties properties, TokenValidationParameters validationParameters)
at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleRemoteAuthenticateAsync()
--- 内部异常堆栈跟踪结束 ---
at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
问题原因及修复方案
问题根源
GetAuthenticationResultForAppAsync是用于客户端凭证流(Client Credentials Flow)的方法,该流属于无用户参与的应用级认证,不会返回IdToken,因此result.IdToken必然为null,这就是导致空指针异常的原因。而当前场景是用户登录的授权码流(Authorization Code Flow),需要处理与用户身份相关的令牌。
修复步骤
替换错误的令牌获取方法
取消注释await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, options.Scope);,移除GetAuthenticationResultForAppAsync的调用,该方法会将用户的令牌缓存到MSAL中,同时可以直接从上下文获取IdToken。正确传递IdToken
在context.HandleCodeRedemption中,直接使用context.TokenEndpointResponse.IdToken,这是Azure AD返回的原始IdToken,无需通过应用流方法获取。
修改后的OnAuthorizationCodeReceived事件代码如下:
options.Events.OnAuthorizationCodeReceived = async context => { context.HandleCodeRedemption(); var _tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); // 将用户令牌缓存到MSAL中 await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, options.Scope); // 使用AD返回的原始IdToken,避免空指针 context.HandleCodeRedemption(null, context.TokenEndpointResponse.IdToken); await handler(context); };
额外说明
- 如果需要获取用户的AccessToken用于调用下游API,后续可以通过
ITokenAcquisition.GetAccessTokenForUserAsync方法从缓存中获取,无需在当前事件中手动处理。 - 确保
options.ResponseType = "code id_token"的配置正确,该配置要求AD在授权码响应中返回IdToken,这是获取用户身份的前提。
内容的提问来源于stack exchange,提问作者Yolanda

