You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Which version of Duende BFF are you using?
Duende.BFF Version="2.2.0"
Which version of .NET are you using?
.Net 8
Describe the bug
Overview:
We are using Duende as our primary identity server, a Multi-Authentication Scheme Setup, and the YARP proxy with an authentication filter. While integrating the BFF Silent Login Endpoint, we encountered an intermittent issue during token refreshing.
Issue Details:
The error occurs inconsistently and only when Multi-Authentication Scheme Setup is configured.
During the token refresh process, the context.GetUserAccessTokenAsync() method is invoked without UserTokenRequestParameters, resulting in the following error:
Yarp.ReverseProxy.Forwarder.HttpForwarder: RequestCreation: An error was encountered while creating the request message. [InvalidOperationException] Unable to load OpenID configuration for configured scheme: Object reference not set to an instance of an object.
at Duende.AccessTokenManagement.OpenIdConnect.UserTokenRequestSynchronization.SynchronizeAsync(String name, Func`1 func) in /_/src/Duende.AccessTokenManagement.OpenIdConnect/UserTokenRequestSynchronization.cs:line 23
at Duende.AccessTokenManagement.OpenIdConnect.UserAccessAccessTokenManagementService.GetAccessTokenAsync(ClaimsPrincipal user, UserTokenRequestParameters parameters, CancellationToken cancellationToken) in /_/src/Duende.AccessTokenManagement.OpenIdConnect/UserAccessTokenManagementService.cs:line 112
at Microsoft.AspNetCore.Authentication.TokenManagementHttpContextExtensions.GetUserAccessTokenAsync(HttpContext httpContext, UserTokenRequestParameters parameters, CancellationToken cancellationToken) in /_/src/Duende.AccessTokenManagement.OpenIdConnect/TokenManagementHttpContextExtensions.cs:line 34
at ServiceTitan.Standalone.ApiGateway.ReverseProxy.BearerTokenTransformProvider.<Apply>b__4_0(RequestTransformContext transformContext) in /src/src/Standalone.ApiGateway/ReverseProxy/BearerTokenTransformProvider.cs:line 19
at Yarp.ReverseProxy.Transforms.Builder.StructuredTransformer.TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, String destinationPrefix, CancellationToken cancellationToken)
at Yarp.ReverseProxy.Forwarder.HttpForwarder.CreateRequestMessageAsync(HttpContext context, String destinationPrefix, HttpTransformer transformer, ForwarderRequestConfig requestConfig, Boolean isStreamingRequest, ActivityCancellationTokenSource activityToken)
at Yarp.ReverseProxy.Forwarder.HttpForwarder.SendAsync(HttpContext context, String destinationPrefix, HttpMessageInvoker httpClient, ForwarderRequestConfig requestConfig, HttpTransformer transformer, CancellationToken cancellationToken)
Analysis:
The issue appears to stem from GetUserAccessTokenAsync not handling missing or incomplete UserTokenRequestParameters when multiple authentication schemes are used.
Workaround:
To address this, I implemented a custom IUserTokenManagementService that ensures UserTokenRequestParameters are initialized correctly for multiple authentication schemes. This includes:
Decorating the original Duende UserAccessAccessTokenManagementService.
Introducing an interface IAuthSchemeManager to dynamically identify authentication schemes.
Properly registering the service with a Transient lifetime, as required by Duende's original implementation.
Note: The SignInScheme is hardcoded to CookieAuthenticationDefaults.AuthenticationScheme because we exclusively use cookie-based authentication, and this was the fastest way to address the issue.
Sample implementation:
/// <summary>/// A custom implementation of <see cref="IUserTokenManagementService"/> for handling multi-authentication scheme policies./// This class decorates the original Duende implementation (<see cref="UserAccessAccessTokenManagementService"/>)/// to address issues with multiple authentication schemes./// /// The issue in the original implementation leads to the following error:/// <code>/// at Duende.AccessTokenManagement.OpenIdConnect.UserTokenRequestSynchronization.SynchronizeAsync(String name, Func`1 func) in /_/src/Duende.AccessTokenManagement.OpenIdConnect/UserTokenRequestSynchronization.cs:line 23/// at Duende.AccessTokenManagement.OpenIdConnect.UserAccessAccessTokenManagementService.GetAccessTokenAsync(ClaimsPrincipal user, UserTokenRequestParameters parameters, CancellationToken cancellationToken) in /_/src/Duende.AccessTokenManagement.OpenIdConnect/UserAccessTokenManagementService.cs:line 112/// at Microsoft.AspNetCore.Authentication.TokenManagementHttpContextExtensions.GetUserAccessTokenAsync(HttpContext httpContext, UserTokenRequestParameters parameters, CancellationToken cancellationToken) in /_/src/Duende.AccessTokenManagement.OpenIdConnect/TokenManagementHttpContextExtensions.cs:line 34/// at ServiceTitan.Standalone.ApiGateway.ReverseProxy.BearerTokenTransformProvider.<Apply>b__4_0(RequestTransformContext transformContext) in /src/src/Standalone.ApiGateway/ReverseProxy/BearerTokenTransformProvider.cs:line 19/// at Yarp.ReverseProxy.Transforms.Builder.StructuredTransformer.TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, String destinationPrefix, CancellationToken cancellationToken)/// at Yarp.ReverseProxy.Forwarder.HttpForwarder.CreateRequestMessageAsync(HttpContext context, String destinationPrefix, HttpTransformer transformer, ForwarderRequestConfig requestConfig, Boolean isStreamingRequest, ActivityCancellationTokenSource activityToken)/// at Yarp.ReverseProxy.Forwarder.HttpForwarder.SendAsync(HttpContext context, String destinationPrefix, HttpMessageInvoker httpClient, ForwarderRequestConfig requestConfig, HttpTransformer transformer, CancellationToken cancellationToken)/// </code>/// </summary>// TODO [Artyom Tonoyan] [26/12/2024]: This implementation is temporary and will be removed when a better solution is found or when Duende or Microsoft fixes the issue.publicclassMultiAuthUserAccessAccessTokenService:IUserTokenManagementService{privatereadonlyIAuthSchemeManager_authSchemeManager;privatereadonlyIUserTokenManagementService_userTokenManagementService;privatereadonlyILogger<MultiAuthUserAccessAccessTokenService>_logger;publicMultiAuthUserAccessAccessTokenService(IAuthSchemeManagerauthSchemeManager,IUserTokenManagementServiceuserTokenManagementService,ILogger<MultiAuthUserAccessAccessTokenService>logger){_authSchemeManager=authSchemeManager;_userTokenManagementService=userTokenManagementService;_logger=logger;}publicTask<UserToken>GetAccessTokenAsync(ClaimsPrincipaluser,UserTokenRequestParameters?parameters=null,CancellationTokencancellationToken=default){parameters=EnsureTokenRequestParameters(parameters);varuserName=user.Identity?.Name??"Unknown";_logger.Debug($"Retrieving access token for user '{userName}' with challenge scheme '{parameters.ChallengeScheme}' and sign-in scheme '{parameters.SignInScheme}'.");return_userTokenManagementService.GetAccessTokenAsync(user,parameters,cancellationToken);}publicTaskRevokeRefreshTokenAsync(ClaimsPrincipaluser,UserTokenRequestParameters?parameters=null,CancellationTokencancellationToken=default){parameters=EnsureTokenRequestParameters(parameters);varuserName=user.Identity?.Name??"Unknown";_logger.Debug($"Revoking refresh token for user '{userName}' with challenge scheme '{parameters.ChallengeScheme}' and sign-in scheme '{parameters.SignInScheme}'.");return_userTokenManagementService.RevokeRefreshTokenAsync(user,parameters,cancellationToken);}/// <summary>/// Ensures that the token request parameters are properly initialized./// </summary>/// <param name="authSchemeProvider">The authentication scheme provider.</param>/// <param name="parameters">The parameters to ensure or initialize.</param>/// <returns>Initialized user token request parameters.</returns>privateUserTokenRequestParametersEnsureTokenRequestParameters(UserTokenRequestParameters?parameters){if(parameters==null){parameters=newUserTokenRequestParameters{ChallengeScheme=_authSchemeManager.GetAuthScheme(),SignInScheme=CookieAuthenticationDefaults.AuthenticationScheme};}else{parameters.ChallengeScheme??=_authSchemeManager.GetAuthScheme();parameters.SignInScheme??=CookieAuthenticationDefaults.AuthenticationScheme;}returnparameters;}}
Registration Details:
// It is important to register UserAccessAccessTokenManagementService as Transient// because the original Duende implementation uses Transient lifetime.// Reference: Duende.AccessTokenManagement.OpenIdConnect.OpenIdConnectTokenManagementServiceCollectionExtensions.// Source code: https://github.com/DuendeSoftware/Duende.AccessTokenManagement/blob/main/src/Duende.AccessTokenManagement.OpenIdConnect/OpenIdConnectTokenManagementServiceCollectionExtensions.cs#L37services.TryAddTransient<UserAccessAccessTokenManagementService>();services.AddTransient<IUserTokenManagementService>(sp =>{varauthSchemeManager=sp.GetRequiredService<IAuthSchemeManager>();varlogger=sp.GetRequiredService<ILogger<MultiAuthUserAccessAccessTokenService>>();varoriginalService=sp.GetRequiredService<UserAccessAccessTokenManagementService>();returnnewMultiAuthUserAccessAccessTokenService(authSchemeManager,originalService,logger);});
Request for Feedback:
Could this issue be addressed directly by Duende or Microsoft?
If this is considered a limitation of Duende's current implementation, I am happy to migrate this ticket to Microsoft or suggest improvements for Duende's roadmap.
Additional Note: If you think the provided information is insufficient, please let me know, and I can find time to create a small project that closely mimics our configuration to help debug the issue.
Please let me know if further clarification or details are needed.
The text was updated successfully, but these errors were encountered:
Which version of Duende BFF are you using?
Duende.BFF Version="2.2.0"
Which version of .NET are you using?
.Net 8
Describe the bug
Overview:
We are using Duende as our primary identity server, a Multi-Authentication Scheme Setup, and the YARP proxy with an authentication filter. While integrating the BFF Silent Login Endpoint, we encountered an intermittent issue during token refreshing.
Issue Details:
context.GetUserAccessTokenAsync()
method is invoked withoutUserTokenRequestParameters
, resulting in the following error:Analysis:
The issue appears to stem from
GetUserAccessTokenAsync
not handling missing or incompleteUserTokenRequestParameters
when multiple authentication schemes are used.Workaround:
To address this, I implemented a custom
IUserTokenManagementService
that ensuresUserTokenRequestParameters
are initialized correctly for multiple authentication schemes. This includes:UserAccessAccessTokenManagementService
.IAuthSchemeManager
to dynamically identify authentication schemes.Note: The
SignInScheme
is hardcoded toCookieAuthenticationDefaults.AuthenticationScheme
because we exclusively use cookie-based authentication, and this was the fastest way to address the issue.Sample implementation:
Registration Details:
Request for Feedback:
Additional Note: If you think the provided information is insufficient, please let me know, and I can find time to create a small project that closely mimics our configuration to help debug the issue.
Please let me know if further clarification or details are needed.
The text was updated successfully, but these errors were encountered: