Next-Auth获取的Azure AD B2C id_token调用.NET Core Web API报invalid_token
我们有一个Next.js应用,通过Next-Auth对接Azure AD B2C完成认证,成功获取到JWT格式的id_token。但在Swagger中使用该id_token调用.NET Core Web API时,收到401 Unauthorized错误,响应头www-authenticate返回:
www-authenticate: Bearer error="invalid_token"
同时后端日志显示:
info: Microsoft.IdentityModel.LoggingExtensions.IdentityLoggerAdapter[0] IDX10242: Security token: '[PII of type 'Microsoft.IdentityModel.JsonWebTokens.JsonWebToken' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' has a valid signature. info: Microsoft.IdentityModel.LoggingExtensions.IdentityLoggerAdapter[0] IDX10239: Lifetime of the token is valid. info: Microsoft.IdentityModel.LoggingExtensions.IdentityLoggerAdapter[0] IDX10234: Audience Validated.Audience: '4a61b4f1-8e3f-488a-ad74-a491b569b0eb' info: Microsoft.IdentityModel.LoggingExtensions.IdentityLoggerAdapter[0] IDX10245: Creating claims identity from the validated token: '[PII of type 'Microsoft.IdentityModel.JsonWebTokens.JsonWebToken' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
日志显示token签名有效、生命周期合法、受众验证通过,且已创建声明身份,但仍返回invalid_token,请问原因是什么?
相关代码
Next-Auth配置代码
import type { NextAuthOptions } from 'next-auth'; import AzureADB2CProvider from 'next-auth/providers/azure-ad-b2c'; const options: NextAuthOptions = { providers: [ AzureADB2CProvider({ tenantId: process.env.AZURE_AD_B2C_TENANT_NAME, clientId: process.env.AZURE_AD_B2C_CLIENT_ID || '', clientSecret: process.env.AZURE_AD_B2C_CLIENT_SECRET || '', primaryUserFlow: process.env.AZURE_AD_B2C_PRIMARY_USER_FLOW, checks: ['pkce'], client: { token_endpoint_auth_method: 'none', client_secret: process.env.AZURE_AD_B2C_CLIENT_SECRET }, authorization: { params: { scope: 'offline_access openid' } } }) ], session: { strategy: 'jwt' // Use JWTs instead of session cookies }, secret: process.env.NEXTAUTH_SECRET, callbacks: { jwt({ token, account }) { const result = token; // If it's the first time signing in, persist the access token from the provider if (account) { result.accessToken = account.id_token; } return result; } } }; export default options;
appsettings.json中的AzureAdB2C配置
"AzureAdB2C": { "Instance": "hidden", "Domain": "hidden.onmicrosoft.com", "TenantId": "hidden", "ClientId": "hidden", "ClientSecret": "hidden", "SignUpSignInPolicyId": "hidden", "CallbackPath": "/signin-oidc", "Audience": "hidden", "SignedOutCallbackPath": "/signout-callback-oidc" }
.NET Core Web API的Program.cs代码
using System.Reflection; using Asp.Versioning; using FluentValidation; using MediatR; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Options; using Microsoft.Identity.Web; using Swashbuckle.AspNetCore.SwaggerGen; var builder = WebApplication.CreateBuilder(args); // Azure builder.AddAzureAppConfiguration(StartupLogger.Current); builder.Services.AddApplicationInsightsTelemetry(options => options.EnableAdaptiveSampling = false); // Common builder.Services .AddPersistence(StartupLogger.Current) .AddInfrastructure(builder.Environment, builder.Configuration); // Host builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")); builder.Services.AddAuthorization(); builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly()); builder.Services.AddValidatorsFromAssemblyContaining<Program>(); builder.Services.AddMediatR(configure => { configure.RegisterServicesFromAssemblyContaining<Program>(); configure.AddBehavior(typeof(IPipelineBehavior<,>), typeof(MetricsBehavior<,>)); configure.AddBehavior(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>)); configure.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); }); builder.Services.AddFeatures(builder.Configuration); // See: https://github.com/dotnet/aspnet-api-versioning/tree/main/examples/AspNetCore/WebApi/MinimalOpenApiExample builder.Services.AddEndpointsApiExplorer(); builder.Services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1); options.ReportApiVersions = true; options.AssumeDefaultVersionWhenUnspecified = true; options.ApiVersionReader = new UrlSegmentApiVersionReader(); }) .AddApiExplorer(options => { options.GroupNameFormat = "'v'V"; options.SubstituteApiVersionInUrl = true; }); builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>(); builder.Services.AddSwaggerGen(x => x.OperationFilter<SwaggerDefaultValues>()); var app = builder.Build(); var apiVersionSet = app.NewApiVersionSet() .HasApiVersion(new ApiVersion(1)) .ReportApiVersions() .Build(); app.UseSwagger(); app.UseSwaggerUI(options => { var descriptions = app.DescribeApiVersions(); // Build a swagger endpoint for each discovered API version foreach (var description in descriptions) { var url = $"/swagger/{description.GroupName}/swagger.json"; var name = description.GroupName.ToUpperInvariant(); options.SwaggerEndpoint(url, name); } // RoutePrefix is set to an empty string to serve Swagger UI at the application's root instead of /swagger. options.RoutePrefix = string.Empty; }); app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); var group = app.MapGroup("api/v{version:apiVersion}") .WithApiVersionSet(apiVersionSet); group.MapEndpoints(); app.Run(); public partial class Program;
核心原因:误用id_token调用API
id_token的设计目的是让客户端验证用户身份,而非用于API授权。虽然Azure AD B2C的id_token会通过签名、生命周期、受众验证,但.NET Core Web API的Microsoft Identity Web中间件默认会额外验证token的typ(类型)和azp(授权方)等声明,同时API应该使用access_token而非id_token来调用。
从你的Next-Auth配置可以看到,你将account.id_token赋值给了result.accessToken,这相当于把身份验证token当作授权token使用,这是错误的。
具体修复步骤
Next-Auth配置中获取正确的access_token
修改jwt回调,将account.access_token而非account.id_token赋值给result.accessToken:callbacks: { jwt({ token, account }) { const result = token; if (account) { result.accessToken = account.access_token; // 替换为access_token result.idToken = account.id_token; // 可选:保留id_token用于客户端身份验证 } return result; } }同时,需要在AzureADB2CProvider的scope中添加API的权限范围,比如你的API的client ID或自定义范围:
authorization: { params: { scope: 'offline_access openid https://yourtenant.onmicrosoft.com/yourapi/api-access' } }验证API配置中的Audience
确保appsettings.json中的Audience字段值与你请求access_token时指定的API范围一致(通常是API的client ID或完整的范围URL)。启用PII日志排查细节
如果问题仍存在,在Program.cs中启用PII日志,查看完整的token验证错误信息:builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(options => { builder.Configuration.Bind("AzureAdB2C", options); options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { Console.WriteLine(context.Exception.ToString()); return Task.CompletedTask; } }; }, options => builder.Configuration.Bind("AzureAdB2C", options)); // 启用PII日志 Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
内容的提问来源于stack exchange,提问作者nop

