AzureAD / microsoft-identity-web

Helps creating protected web apps and web APIs with Microsoft identity platform and Azure AD B2C
MIT License
682 stars 217 forks source link

Authentication Issue for external URL ( Power BI , .net Core 3.1, Azure AD App,IIS) #1617

Closed josephthomas5566 closed 2 years ago

josephthomas5566 commented 2 years ago

We are building a .net core app to be hosted on IIS, which has an external/Public URL which converts into internal URL with app Proxy. The .net core app is created as page within the main page.

We have a public URL at : https://product.portal.example.org/link ,which internally via application proxy get redirected to https://product.example.org/link.

We have an AD App authentication which automatically reconstructs the return URI but uses "http://product.example.org/link" instead of "http://product.portal.example.org/link". Using custom redirect_uri we are able to redirect them to http://product.portal.example.org/link/signin-oidc. But our issue is after the authentication it is not taking us to the app page.

But within VPN , When the tester access the link https://product.example.org/link and logins (via AD APP) it come back with link on the URL https://product.example.org/link/signin-oidc and works fine.

But outside , When the tester access the link https://product.portal.example.org/link and logins (via AD APP) it come back with link on the URL https://product.portal.example.org/link/signin-oidc(GET). Then when it POST the final link : https://product.portal.example.org/link/home/app , it never succeeds. It continues to send the page to signin-oidc(get) and post page ( Like a LOOP** .. )

We are not sure what the issue is and has been stuck for some time. Any help is appreciated.

jmprieur commented 2 years ago

@josephthomas5566 : is it an ASP.NET Core app/ If yes, did you see https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-5.0#troubleshoot ?

josephthomas5566 commented 2 years ago

yes . .net core app.. I will check this. Base code was taken from https://github.com/microsoft/PowerBI-Developer-Samples/tree/master/.NET%20Core/Embed%20for%20your%20organization/UserOwnsData

To this we added custom redirect_uri

josephthomas5566 commented 2 years ago

Unfortunately it is not working. Not sure if the redirect code is correct. This is still looping ( or not sure if the authentication is happening..) .. The Azure AD app has the return URI configured.. Anythoughts

      services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
            {
                options.SaveTokens = true; // this saves the token for the downstream api
                options.Events = new OpenIdConnectEvents
                {
                    OnRedirectToIdentityProvider = async ctxt =>
                    {

                        ctxt.ProtocolMessage.RedirectUri = "https://product.example.org/link/signin-oidc ";
                        await Task.Yield();
                    }
                };
            });
jmprieur commented 2 years ago

@josephthomas5566, do you use Microsoft.Identity.Web?

josephthomas5566 commented 2 years ago

Yes.. using Both. using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI;

Another observation is Oauth2.0 tracer is not returning "clientinfo" when we do redirect_uri ,but gives that when we do without that( on Internal- non public URL) .. Just an observation.. Not sure what is really going on

jmprieur commented 2 years ago

@josephthomas5566 : I transferred the issue to the right repo

josephthomas5566 commented 2 years ago

Thanks .. hopefully someone from this team can help us here...

jmprieur commented 2 years ago

@josephthomas5566

You should chain the events, not override them

services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
 options.SaveTokens = true; // this saves the token for the downstream api
 var existingOnRedirectToIdentityProvider  = options.Events.OnRedirectToIdentityProvider ;
 options.Events.OnRedirectToIdentityProvider = async ctxt =>
  {
   await existingOnRedirectToIdentityProvider(ctxt);
   ctxt.ProtocolMessage.RedirectUri = "https://product.example.org/link/signin-oidc ";
   }
  };
});

But I suspect that you'd need to use the forward headers instead, as I indicated above cc: @Tratcher

josephthomas5566 commented 2 years ago

Yes. I have done that.. and have called the below in configure.. app.UseForwardedHeaders(new ForwardedHeadersOptions() { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto });

For the other items the order is as below :ConfigureServices

          services.Configure<ForwardedHeadersOptions>(options =>
                            {
                                options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | 
                                    ForwardedHeaders.XForwardedProto;
                                // Only loopback proxies are allowed by default.
                                // Clear that restriction because forwarders are enabled by explicit 
                                // configuration.
                                options.KnownNetworks.Clear();
                                options.KnownProxies.Clear();
                            });

            services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAd")
                .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
                .AddMicrosoftGraph(graphBaseUrl, userReadScope)
                .AddSessionTokenCaches();

                              services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
            {
                options.SaveTokens = true; // this saves the token for the downstream api
                var existingOnRedirectToIdentityProvider  = options.Events.OnRedirectToIdentityProvider ;
                options.Events.OnRedirectToIdentityProvider = async ctxt =>
                {
                            await existingOnRedirectToIdentityProvider(ctxt);
                            ctxt.ProtocolMessage.RedirectUri = "https://product.example.org/link/signin-oidc";
                };
              });

            services.AddControllersWithViews(options =>
            {
                var policy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();
                options.Filters.Add(new AuthorizeFilter(policy));
            }).AddMicrosoftIdentityUI();

            services.AddRazorPages();
josephthomas5566 commented 2 years ago

i had a typo and fixed it.. Now i am back to this error (that is interesting .. from infinte loop back to saying the POST is not accessible)..

This product.portal.example.com page can’t be found No webpage was found for the web address: https://product.portal.example.com/link/signin-oidc HTTP ERROR 404

So not sure if the authentication went through and got stuck on the POST

Tratcher commented 2 years ago

This

app.UseForwardedHeaders(new ForwardedHeadersOptions()
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

Is conflicting with this

                            services.Configure<ForwardedHeadersOptions>(options =>
                            {
                                options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | 
                                    ForwardedHeaders.XForwardedProto;
                                // Only loopback proxies are allowed by default.
                                // Clear that restriction because forwarders are enabled by explicit 
                                // configuration.
                                options.KnownNetworks.Clear();
                                options.KnownProxies.Clear();
                            });

Configure<ForwardedHeadersOptions> only works with the no parameter version of UseForwardedHeaders(). Consolidate the settings in one place or the other.

Avoid directly overriding RedirectUri, it should be auto-generated based on the current request. If it's wrong then it's better to fix the request parameters. It sounds like you need to enable ForwardedHeaders.XForwardedHost.

Sharing a Fiddler trace would also help us understand the issue.

josephthomas5566 commented 2 years ago

I have removed the conflict in the latest iteration.. Can you let me know how to 1) enable ForwardedHeaders.XForwardedHost 2) how to fix the request parameters to autogenerate. ( Our Public URL is different from Internal and redirectUri is autogenerated based on internal URL though the request starts from External

Tratcher commented 2 years ago

2. Our Public URL is different from Internal and redirectUri is autogenerated based on internal URL though the request starts from External

Is this the expected and actual values? Expected: https://product.portal.example.org/link/signin-oidc Actual: https://product.example.org/link/signin-odic

You'll need to look at the output from https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-5.0#troubleshoot to see what the request headers are.

Can you let me know how to enable ForwardedHeaders.XForwardedHost

options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
josephthomas5566 commented 2 years ago

Expected: https://product.portal.example.org/link/signin-oidc Actual: https://product.example.org/link/signin-odic

Request Method: GET Request Scheme: https Request Path: Request Headers: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Connection: Keep-Alive Host: product.example.org User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: none Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 X-Forwarded-For: <valid Ip address X-MS-Proxy: AzureAD-Application-Proxy

josephthomas5566 commented 2 years ago

Enabled the logger ..it says ..

Microsoft.Identity.Client.MsalServiceException: A configuration issue is preventing authentication - check the error message from the server for details.You can modify the configuration in the application registration portal. See https://aka.ms/msal-net-invalid-client for details. Original exception: AADSTS500112: The reply address 'https://product.example.org/link/signin-oidc' does not match the reply address 'https://product.portal.example.org/link/signin-oidc' provided when requesting Authorization code.

The applicaiton looks like it is still giving actual value though we are forcing a redirect..Any thoughts?

Tratcher commented 2 years ago

Host: product.example.org is your problem. Which proxy are you using? It doesn't look like it's setting the x-forwarded-Host header with the original public value. Your backend app has no way of knowing what the public host value is.

If you can't figure out how to get that enabled on the proxy then one way to work around it is like this:

app.Use((context, next) =>
{
    context.Request.Host = "product.portal.example.org";
    return next();
});
josephthomas5566 commented 2 years ago

Seeing some light at the end of the tunnel. The initil authentication is going through.. Need to see if the authentication remain valid for the components i pullled in.

Changes

With this code do you still need forwarders?

Tratcher commented 2 years ago

With this code do you still need forwarders?

You may still need them for the scheme ('https'), or you could set that the same way if you always require https.

jennyf19 commented 2 years ago

Are you unblocked on this @josephthomas5566 ?