ASP.NET Core无法从OpenID Connect获取id_token与access token问题排查
我有一个ASP.NET Core Web应用,需要将Azure AD的access token发送给PHP脚本,但使用GetTokenAsync从HttpContext获取token时始终返回null。调试发现GetTokenAsync被CookieAuthenticationHandler.HandleAuthenticateOnceAsync()中断,该方法返回AuthenticateResult.Failure。
已完成的配置
1. Startup.cs中的OpenID Connect配置
public void ConfigureServices(IServiceCollection services) { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddControllersWithViews(); services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) .AddOpenIdConnect(options => { IConfigurationSection sectionOptions = AppConfiguration?.GetSection("OpenIdConnectOptions"); if(sectionOptions?.GetChildren()?.Any() == true) { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.Authority = sectionOptions["Authority"]; options.ClientId = sectionOptions["ClientId"]; options.ClientSecret = sectionOptions["ClientSecret"]; options.CallbackPath = new PathString(sectionOptions["CallbackPath"]); options.SignedOutCallbackPath = new PathString(sectionOptions["SignedOutCallbackPath"]); options.RemoteSignOutPath = new PathString(sectionOptions["RemoteSignOutPath"]); options.ResponseType = OpenIdConnectResponseType.Code; options.ResponseMode = OpenIdConnectResponseMode.Query; options.GetClaimsFromUserInfoEndpoint = true; List<string> scopes = (from curSubsectionScope in sectionOptions.GetSection("Scope")?.GetChildren() where !string.IsNullOrWhiteSpace(curSubsectionScope.Value) select curSubsectionScope.Value.Trim().ToLower())?.ToList(); options.Scope?.Clear(); scopes?.ForEach(options.Scope.Add); options.MapInboundClaims = false; options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name; options.TokenValidationParameters.RoleClaimType = ClaimTypes.Role; } } }
2. appsettings.json配置
"OpenIdConnectOptions": { "Authority": "https://login.microsoftonline.com/{my-tenant-id}/v2.0/", "ClientId": "{my-client-id}", "ClientSecret": "{my-client-secret}", "CallbackPath": "/signin-oidc", "SignedOutCallbackPath": "/signout-callback-oidc", "RemoteSignOutPath": "/signout-oidc", "RedirectUri": "http://localhost:80/azure", "SaveTokens": true, "Scope": [ "openid", "offline_access" ] },
3. Azure AD应用设置
az ad app show --id $clientId --query "web.redirectUris" [ "https://localhost/signin-oidc", "https://localhost/signout-callback-oidc", "https://localhost/signout-oidc", "http://localhost/azure" ] az ad app permission list --id $clientId [ { "resourceAccess": [ { "id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d", # User.Read "type": "Scope" }, { "id": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9", # Application.ReadWrite.All "type": "Role" }, { "id": "37f7f235-527c-4136-accd-4a02d197296e", # openid "type": "Scope" }, { "id": "7427e0e9-2fba-42fe-b0c0-848c9e6a8182", # offline_access "type": "Scope" } ], "resourceAppId": "00000003-0000-0000-c000-000000000000" # https://graph.microsoft.com } ]
出错的代码
string access_token = await httpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); string id_token = await httpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
问题排查与解决
1. 确保SaveTokens配置被正确加载
你的appsettings.json中设置了SaveTokens: true,但Startup的OpenIdConnect配置代码中没有读取这个参数。默认SaveTokens为false,token不会被保存到Cookie中,导致GetTokenAsync返回null。需要在AddOpenIdConnect的配置块中添加:
options.SaveTokens = sectionOptions.GetValue<bool>("SaveTokens");
2. 补充有效API作用域
当前配置的Scope只有openid和offline_access,这两个作用域仅用于获取id_token和refresh token,不会返回可调用API的access token。需要添加具体的API权限作用域,比如已授权的User.Read,修改appsettings.json的Scope:
"Scope": [ "openid", "offline_access", "https://graph.microsoft.com/User.Read" ]
3. 启用认证中间件
检查Configure方法中是否添加了认证中间件,缺少的话会导致认证流程不执行:
app.UseAuthentication(); app.UseAuthorization();
4. 验证回调地址一致性
确保Azure AD应用中的redirectUris包含应用实际运行的CallbackPath(/signin-oidc),注意协议(http/https)和端口必须与应用运行地址完全匹配,否则回调失败会导致token无法保存。
5. 排查认证失败的具体原因
通过Cookie认证事件捕获失败详情,精准定位问题:
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.Events = new CookieAuthenticationEvents { OnAuthenticationFailed = context => { // 记录context.Exception的详细信息,比如签名验证失败、Cookie过期等 return Task.CompletedTask; } }; })
内容的提问来源于stack exchange,提问作者Stanislav Panferov

