DuendeSoftware / BFF

Framework for ASP.NET Core to secure SPAs using the Backend-for-Frontend (BFF) pattern
Other
333 stars 75 forks source link

Starting with BFF 2.0.1 WithAccessToken leads to error 401 #179

Closed boekhoffj2801 closed 1 year ago

boekhoffj2801 commented 1 year ago

Which version of Duende BFF are you using? Duende.BFF 2.0.0 Duende.BFF.YARP 2.0.0

Which version of .NET are you using? .net 7 Describe the bug We are using a YARP configuration which adds an user access token on redirection. This is working fine up to version 2.0.0 even, if no user has logged in.

"ReverseProxy": { "Routes": { "gProductConfiguratorRoute": { "ClusterId": "gProductConfiguratorCluster", "Match": { "Path": "/gProductConfigExtern.ProductConfigurator/{**remainder}" }, "Metadata": { "Duende.Bff.Yarp.TokenType": "User" } } }, "Clusters": { "gProductConfiguratorCluster": { "Destinations": { "gProductConfigurator/destination1": { "Address": "https://localhost:7105/" } } } } }

After update to BFF 2.0.1 the redirection fails with error 401:

`[06:22:12 Debug] Duende.AccessTokenManagement.OpenIdConnect.UserAccessAccessTokenManagementService No active user. Cannot retrieve token

[06:22:12 Warning] Duende.Bff.Yarp.AccessTokenRequestTransform Access token is missing. token type: 'User', local path: 'gProductConfiguratorRoute', detail: 'Missing access token'

[06:22:12 Information] Yarp.ReverseProxy.Forwarder.HttpForwarder Not Proxying, a 401 response was set by the transforms.`

Our code to configure YARP looks like

        var reverseProxyConfig = builder.Configuration.GetSection("ReverseProxy");
        builder.Services.AddReverseProxy()
            .AddBffExtensions()
            .LoadFromConfig(reverseProxyConfig);

and for the pipeline

        app.UseAuthentication();
        app.UseBff();
        app.UseAuthorization();
        app.MapBffManagementEndpoints();
        app.MapBffReverseProxy(app => app.UseAntiforgeryCheck());

I found an extension method "WithOptionalUserAccessToken()", but if I add this after the last line, I get an error 500:

[06:31:28 Error] Serilog.AspNetCore.RequestLoggingMiddleware HTTP POST /gProductConfigExtern.ProductConfigurator/GetGroups responded 500 in 40.2883 ms System.InvalidOperationException: Sequence contains no matching element at System.Linq.ThrowHelper.ThrowNoMatchException() at System.Linq.Enumerable.First[TSource](IEnumerable1 source, Func2 predicate) at Microsoft.AspNetCore.Builder.BffRemoteApiEndpointExtensions.GetBffRemoteApiEndpointMetadata(EndpointBuilder builder) in /_/src/Duende.Bff/Configuration/BffRemoteApiEndpointExtensions.cs:line 17 at Microsoft.AspNetCore.Builder.BffRemoteApiEndpointExtensions.<>c3`1.b30(EndpointBuilder endpointBuilder) in //src/Duende.Bff/Configuration/BffRemoteApiEndpointExtensions.cs:line 70 at Yarp.ReverseProxy.Routing.ProxyEndpointFactory.CreateEndpoint(RouteModel route, IReadOnlyList1 conventions) at Yarp.ReverseProxy.Management.ProxyConfigManager.CreateEndpoints() at Yarp.ReverseProxy.Management.ProxyConfigManager.get_Endpoints() at Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.CreateEndpointsUnsynchronized() at Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.EnsureEndpointsInitialized() at Microsoft.AspNetCore.Routing.DataSourceDependentCache1.Initialize() at System.Threading.LazyInitializer.EnsureInitializedCore[T](T& target, Boolean& initialized, Object& syncLock, Func1 valueFactory) at Microsoft.AspNetCore.Routing.Matching.DataSourceDependentMatcher..ctor(EndpointDataSource dataSource, Lifetime lifetime, Func1 matcherBuilderFactory) at Microsoft.AspNetCore.Routing.Matching.DfaMatcherFactory.CreateMatcher(EndpointDataSource dataSource) at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.InitializeCoreAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.g__AwaitMatcher|8_0(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task`1 matcherTask) at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)

From our point of view there is a breaking change between version 2.0.0 and 2.0.1. Is there a way of configuration to get back the old behavior? We need an optional user access token to answer generally every request but give detailed information if a user has logged in. I found no examples to get "WithOptionalUserAccessToken" working. Can you provide a sample for such scenario?

To Reproduce Use version 2.0.0 with YARP config that adds user access token. Update to version 2.0.1. On same method call get error 401.

Expected behavior Either "routeConfig.WithAccessToken(TokenType.User);" should add a token optionally like before or provide an example, how to add an optional user access token, please.

Log output/exception with stacktrace

(see above)

With kind regards, J. Boekhoff

brockallen commented 1 year ago

Starting with BFF 2.0.1

I suspect you mean upgrading to 2.1.0?

In your current use it sounds like you want API calls to be sent to the API even if there is no user token, so anonymously. Is this correct?

krilek commented 1 year ago

Connecting to this issue as we struggle with the same problem.

In your current use it sounds like you want API calls to be sent to the API even if there is no user token, so anonymously. Is this correct?

Exactly, previously it was possible to use YARP JSON metadata options and access anonymous remote API endpoints. Right now it returns 401.

brockallen commented 1 year ago

@josephdecock Please have a look into this.

josephdecock commented 1 year ago

@krilek can you double check your version number please? Are you on 2.1.0?

boekhoffj2801 commented 1 year ago

Sorry, I didn't got an notification for changes to this request. Yes, you are right. If tried to update to 2.1.0. And then an anonymous request to an api call gives 401. If a user has logged in , everything works fine.

josephdecock commented 1 year ago

In earlier versions of the BFF, we used YARP v1.x, which doesn't have a mechanism to cancel proxy requests. That meant that if we didn't have an access token we had to fall back to anonymous access. But what we always would have preferred to do (as the default behavior) is what we do in the simple Http forwarder - return a 401 error.

Since we've upgraded our dependency on YARP to v2, we now take advantage of its new ability to cancel proxied http requests. This gives us consistency between the two styles of remote API definitions.

So when we made this change we were really fixing a long standing design bug. What was missing though, and what is added in the PR linked to this thread, is the ability to add metadata to YARP routes so that they require an optional user access token.

boekhoffj2801 commented 1 year ago

I'm glad to see, that the missing use case of optional user access token will be supported in the future. Could you provide an example or snippet how to get this working again and work arround the breaking change?

brockallen commented 1 year ago

@josephdecock will be working on the new docs showing you how to use this. We have a patch 2.1.1 with the updated API.

josephdecock commented 1 year ago

The PR for the new docs is here. The TL;DR is, in 2.1.1 you can call .WithOptionalUserToken on a yarp route configured with code, or more generally set Duende.Bff.Yarp.OptionalUserToken in the metadata of a yarp route to get optional user access tokens.