fancyDevelopment / Fancy.ResourceLinker

A set of libraries to easily create API Gateways, Backend for Frontends (BFF) and truly RESTful Web APIs based on ASP.NET Core
Apache License 2.0
20 stars 5 forks source link

ResourceLinker.Gateway issue: Clients must send a client_secret when redeeming a confidential grant #30

Closed Pieter-1337 closed 4 months ago

Pieter-1337 commented 5 months ago

Issue when Updating from Version 0.0.7 to 0.0.8 or 0.0.9

image

When i downgrade the package back to Version 0.0.7 all works as it should.

image No changes made except package upgrade. Issue exists on both 0.0.8 and 0.0.9

fancyDevelopment commented 5 months ago

Hi Peter, I am working currently at two customer projects where the auth servers don't comply to the standards. In the course of this work I also refactored a lot how the gateway authenticates to the microservices. In introduced a so called auth strategy which can be individually configured for each route in the gateway. See a first example here: https://github.com/fancyDevelopment/Fancy.ResourceLinker-Sample/blob/bug/entra-id/src/Sample.Gateway/appsettings.json Currently all works fine with the right new configuration with Azure EntraId, Auth0 and now I get the next customer with again another auth server. I plan to check how it is working there and then the lib is in production with 4 different auth servers. If this is working then I plan to release a 1.0.0 version an then I will not implement breaking changed within the major version. As long as a 0 is leading the version number I feel free to always introduce breaking changes ;-)

PieterBracke commented 5 months ago

Hi Daniel, I tried setting my the Authentication section of the routes in routeSettings to AzureOnBehalfOf, TokenPassThrough, and ClientCredentials only. I am a bit unsure to what I should set it?

I authenticate via Azure AD B2C: On the AzureOnBehalfOf setting I still got the same error as in the ticket here. On the ClientCredentialsOnly I get:

An unhandled exception occurred while processing the request.
HttpRequestException: Response status code does not indicate success: 400 (Bad Request).
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()

Stack Query Cookies Headers Routing
HttpRequestException: Response status code does not indicate success: 400 (Bad Request).
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
Fancy.ResourceLinker.Gateway.Routing.Auth.ClientCredentialOnlyAuthStrategy.GetTokenViaClientCredentialsAsync()
Fancy.ResourceLinker.Gateway.Routing.Auth.ClientCredentialOnlyAuthStrategy.GetAccessTokenAsync()
Fancy.ResourceLinker.Gateway.Routing.Auth.ClientCredentialOnlyAuthStrategy.SetAuthenticationAsync(HttpContext context)
Fancy.ResourceLinker.Gateway.Routing.GatewayPipeline+<>c+<<UseGatewayPipeline>b__0_0>d.MoveNext()
Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)
Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
Fancy.ResourceLinker.Gateway.AntiForgery.GatewayAntiForgery+<>c__DisplayClass3_0+<<UseXsrfHeaderChecks>b__0>d.MoveNext()
Fancy.ResourceLinker.Gateway.Authentication.GatewayAuthentication+<>c+<<UseGatewayAuthentication>b__3_0>d.MoveNext()
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

I add here a version of my config where i removed/replaced the sensitive information, but you should be able to point me in the right direction I think :)


  "Authentication": {
    "Authority": "https://pbrcplaygroundb2c.b2clogin.com/pbrcplaygroundb2c.onmicrosoft.com/B2C_1_susi/v2.0/",
    "ClientId": "CLientId", //ClentId of the audience (API) we are getting a token for
    "ClientSecret": "ClientSecret", //ClientSecret of the audience (API) we are getting a token for
    "AuthorizationCodeScopes": "openid email profile offline_access https://pbrcplaygroundb2c.onmicrosoft.com/....../access_api",
    "QueryUserInfoEndpoint": false,
    //Set cookie timeout => Look into how long this should be for us!
    "SessionTimeoutInMin": 1,
    "UniqueIdentifierClaimType": "name" //"preferred_username"
  },
  "Routing": {
    //These settings hide our entries behind the gateway!
    "Routes": {
      //Here we can define the different API routes (urls, we wish to access through our BFF)
      "API": {
        "BaseUrl": "https://localhost:44340",
        //PathMatch => If a request is made and there is no controllerAction that fulfilles the request and the path matches here, we just propagate to the actual API!
        "PathMatch": "api/{**path}",
        "Authentication": {
          "Strategy": "ClientCredentialsOnly"
        },
        "EnforceAuthentication": true
      },
      //Here we can define the different frontends we wish to access through our BFF)
      "Portal": {
        //By specifying the baseUrl of our front-end here we propagate all request for our SPA => example go to https://localhost:44305 and we will load our SPA behind the gateway
        "BaseUrl": "https://localhost:4200",
        "PathMatch": "{**path}", //Always propagate these requests
        "Authentication": {
          "Strategy": "ClientCredentialsOnly",
          "Options": {
            "Scope": "openid email profile offline_access https://pbrcplaygroundb2c.onmicrosoft.com/...../access_api"
          }
        },
        "EnforceAuthentication": false //toggle to start authentication flow when navigating to index.html or with an action to the /login endpoint once navigated to the frontend
      }
      /*
      "Portal2": {
      ....
      }
      */
    }
  }
}```

I also noticed the enforceAuthentication flag was removed from the routes in the routeSetting, I thought this was quite convenient since not all users on a frontend should login immediately?
fancyDevelopment commented 5 months ago

Hi Peter, as far as I know Azure AD B2C does not support the Azure on Behalf of flow. So I think you should use the "TokenPassThrough" strategy. I just compiled a documentation of the new auth capabilities. You can find it here: https://github.com/fancyDevelopment/Fancy.ResourceLinker/blob/feature/auth-split/doc/features/authentication.md Would you like read through and provide some feedback to me if this is of help to you?

Pieter-1337 commented 5 months ago

@fancyDevelopment Hi Daniel, I did some further research, and it seems that in the published 0.0.9 the client Secret is missing from the cookieOptions! I made a branch with my fork from the github repo and attached my solution that was failing that version of the gateway and models and all worked fine, however in the 0.0.9 nuget package version the issue keeps arrising concerning the client secret. I think the difference lies in this:

Published version 0.0.9: image

The repo fork : image

So I guess the V0.0.9 has a critical issue => the missing client secret in the options . Can you confirm this is the issue?

fancyDevelopment commented 4 months ago

Hi @Pieter-1337, thanks for your issue. I checked this and I can tell you that it was my intention to remove the ClientSecret. The reason is that my idea was that the gateway shall behave like a public client. A client which is generally not trused to keep some secrets, similar like a single page application in the browser. But since I now implemented the token exchage, its neccessary for the gateway to keep secrets. So I am fine to revert this change. What I have not checked yet is, if this change really makes problems with AD B2C. I think AD B2C should support public clients too.

Pieter-1337 commented 4 months ago

@fancyDevelopment Do you mind elaborating on why the BFF should be a public client? In the end it lives on a server and not in a browser runtime (like a spa would), those have been the differentiators between a public and confidential client for me. I am curious why we would want to register a BFF as a public client.

fancyDevelopment commented 4 months ago

@Pieter-1337 You are completely right with what you have written. And I have the same thoughts in my mind. At the very beginning, I thought it could be a good idea if the BFF is as thin as possible and therefore started to treat it as a public client. But with token exchange there is no other way now than to treat it as a confidential client. And all your arguments apply! So I reverted this change. If the ClientSecret is not null or whitespace it gets added to the configuration, if not it is still empty. I just merged all my last changes to master. But did not build new nuget packages yet.