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

Duende BFF框架问题:调用端点因缺失Access Token触发401错误

问题:Duende BFF调用端点返回401,提示Access Token缺失

本地调试React SPA用户认证流程,采用IdentityServer6与Duende BFF框架,已按官方文档完成配置,登录流程看似正常,但调用本地及远程端点(包括/bff/user用户信息端点)时,BFF Host持续返回401错误,日志提示Access Token无法找到。

BFF Host日志信息

[23:19:05 Warning] Duende.Bff.Yarp.AccessTokenRequestTransform
Access token is missing. token type: 'Unknown token type', local path: 'Unknown Route', detail: 'Missing access token'

[23:19:05 Information] Yarp.ReverseProxy.Forwarder.HttpForwarder
Not Proxying, a 401 response was set by the transforms.

Identity Server日志信息(显示Token已颁发)

[23:19:04 Information] Duende.IdentityServer.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: Duende.IdentityServer.Endpoints.AuthorizeEndpoint for /connect/authorize

[23:19:04 Information] Duende.IdentityServer.Events.DefaultEventService
{"ClientId": "interactive", "ClientName": null, "RedirectUri": "https://localhost:5002/", "Endpoint": "Authorize", "SubjectId": "1", "Scopes": "openid profile scope2 offline_access", "GrantType": "authorization_code", "Tokens": [{"TokenType": "code", "TokenValue": "****1D-1", "$type": "Token"}], "Category": "Token", "Name": "Token Issued Success", "EventType": "Success", "Id": 2000, "Message": null, "ActivityId": "0HMSBUIP9547T:00000011", "TimeStamp": "2023-07-24T03:19:04.9717252Z", "ProcessId": 49004, "LocalIpAddress": "::1:5001", "RemoteIpAddress": "::1", "$type": "TokenIssuedSuccessEvent"}

[23:19:04 Information] Serilog.AspNetCore.RequestLoggingMiddleware
HTTP GET /connect/authorize responded 302 in 18.3209 ms

[23:19:04 Information] Microsoft.AspNetCore.Hosting.Diagnostics
Request finished HTTP/2 GET https://localhost:5001/connect/authorize?client_id=interactive&redirect_uri=https%3A%2F%2Flocalhost%3A5002%2F&response_type=code&scope=openid%20profile%20scope2%20offline_access&code_challenge=7FjRcNdlHDeguDpLb2CmMSQrFZULfi46FaxRKds-KhY&code_challenge_method=S256&nonce=638257655449622490.ZWVjOGJmYjMtZjdlOC00NjQ1LTgxNTYtM2UxZjdiYjAwMTM0NjNmMjFjYWYtNTA4MC00Yjg4LTgyOWEtMTY5YWIxNDFhMjM4&state=CfDJ8GNxzlZXLyNLt0KDaVzK8HYWEXpzhCOxx2CvvAYf7lImUYvaqSqPOt92ipDF1BQM23yjJ58NcKo7W5m0eftvl3H042zOGJ6mmoBDLjFfjg1o01tH4Y6cE6YxonHxAmr-pkhtas4D0IP-_iRtYqLSPDgquJHpYVqTRAa9HJz6_crmBy0P_0cLHnaU8PmltBADJVH9lF7HWzvkjjbT6MeO8YM92R_mLwhr7uE5UfKqeYV2PxArkamdRKigVBO-gsASJlHSbWa2do2VGhM5itYvKAHJ__IHRAdfoU3CXLSY5ALkUa0Kq9IdP9RANriImkaIfCFaEJHJ3QfvVDAuP529pdIuINXLVi3OHQYZS1O3_bpG&x-client-SKU=ID_NET6_0&x-client-ver=6.21.0.0 - - - 302 0 - 22.6862ms

BFF Host认证配置

public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
{
    // add BFF services and server-side session management
    builder.Services.AddBff()
        .AddRemoteApis()
        .AddServerSideSessions();

    builder.Services.AddAuthentication(options =>
        {
            options.DefaultScheme = "cookie";
            options.DefaultChallengeScheme = "oidc";
            options.DefaultSignOutScheme = "oidc";
        })
        .AddCookie("cookie", options =>
        {
            options.Cookie.Name = "__Host-bff";
            options.Cookie.SameSite = SameSiteMode.Strict;
        })
        .AddOpenIdConnect("oidc", options =>
        {
            options.Authority = "https://localhost:5001";
            options.ClientId = "interactive";
            options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";
            options.CallbackPath = "/";
            options.SignedOutCallbackPath = "/";
            options.ResponseType = "code";
            options.ResponseMode = "query";

            options.UsePkce = true;
            options.GetClaimsFromUserInfoEndpoint = true;
            options.SaveTokens = true;
            options.MapInboundClaims = false;

            options.Scope.Clear();
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("scope2");
            options.Scope.Add("offline_access");

            options.TokenValidationParameters.NameClaimType = "name";
            options.TokenValidationParameters.RoleClaimType = "role";
        });

    return builder.Build();
}

public static WebApplication ConfigurePipeline(this WebApplication app)
{
    app.UseHttpsRedirection();
    app.UseDefaultFiles();
    app.UseStaticFiles();

    app.UseRouting();

    // add CSRF protection and status code handling for API endpoints
    app.UseAuthentication();
    app.UseBff();
    app.UseAuthorization();

    // local API endpoints
    app.MapControllers()
        .RequireAuthorization()
        .AsBffApiEndpoint();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBffManagementEndpoints();
        endpoints.MapRemoteBffApiEndpoint("/api", "https://localhost:7011")
            .RequireAccessToken(TokenType.User);
    });

    return app;
}

Identity Server配置

public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
{
    builder.Services.AddRazorPages();

    var isBuilder = builder.Services.AddIdentityServer(options =>
        {
            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;

            // see https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/
            options.EmitStaticAudienceClaim = true;
        })
        .AddTestUsers(TestUsers.Users);

    // in-memory, code config
    isBuilder.AddInMemoryIdentityResources(Config.IdentityResources);
    isBuilder.AddInMemoryApiScopes(Config.ApiScopes);
    isBuilder.AddInMemoryClients(Config.Clients);


    // if you want to use server-side sessions: https://blog.duendesoftware.com/posts/20220406_session_management/
    // then enable it
    isBuilder.AddServerSideSessions();

    builder.Services.AddAuthentication();

    return builder.Build();
}

public static WebApplication ConfigurePipeline(this WebApplication app)
{
    app.UseStaticFiles();
    app.UseRouting();
    app.UseIdentityServer();
    app.UseAuthorization();

    app.MapRazorPages()
        .RequireAuthorization();

    return app;
}

Identity Server客户端配置

public static IEnumerable<Client> Clients =>
    new Client[]
    {
        // interactive client using code flow + pkce
        new Client
        {
            ClientId = "interactive",
            ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },

            AllowedGrantTypes = GrantTypes.Code,

            RedirectUris = { "https://localhost:5002/" },
            FrontChannelLogoutUri = "https://localhost:5002/signout-oidc",
            PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },

            AllowOfflineAccess = true,
            AllowedScopes = { "openid", "profile", "scope2", "offline_access" }
        },
    };

排查与修复步骤

  • 修正OIDC回调路径:
    BFF中options.CallbackPath = "/";和options.SignedOutCallbackPath = "/";会与SPA根路径冲突,导致OIDC无法正确处理授权码交换流程,token无法保存到会话。修改为:
    options.CallbackPath = "/signin-oidc";
    options.SignedOutCallbackPath = "/signout-callback-oidc";
    
    同时更新IdentityServer客户端配置的RedirectUris为"https://localhost:5002/signin-oidc"。
  • 验证Token会话存储:修复路径后,登录成功可通过调试工具检查BFF的__Host-bff cookie,确认会话中包含access_token。
  • 检查API Scope有效性:确保IdentityServer的Config.ApiScopes中scope2已正确定义且Enabled = true,否则token不会包含该权限范围。
  • 调整Cookie SameSite属性:本地调试时SameSiteMode.Strict可能限制跨域cookie传递,临时改为SameSiteMode.Lax测试,生产环境再按需调整。
  • 确认BFF中间件覆盖范围:确保MapBffManagementEndpoints()已正确注册,/bff/*端点由BFF中间件处理。

内容的提问来源于stack exchange,提问作者Tanksley

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 04:27:02