You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next-Auth获取的Azure AD B2C id_token调用.NET Core Web API报invalid_token

问题:Azure AD B2C id_token调用.NET Core Web API返回401 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使用,这是错误的。

具体修复步骤

  1. 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'
        }
    }
    
  2. 验证API配置中的Audience
    确保appsettings.json中的Audience字段值与你请求access_token时指定的API范围一致(通常是API的client ID或完整的范围URL)。

  3. 启用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 01:24:51