microsoft-identity-web icon indicating copy to clipboard operation
microsoft-identity-web copied to clipboard

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

Open josephthomas5566 opened this issue 2 years ago • 19 comments

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.

josephthomas5566 avatar Feb 01 '22 00:02 josephthomas5566

@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 ?

jmprieur avatar Feb 01 '22 02:02 jmprieur

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 avatar Feb 01 '22 02:02 josephthomas5566

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();
                    }
                };
            });

josephthomas5566 avatar Feb 02 '22 00:02 josephthomas5566

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

jmprieur avatar Feb 02 '22 01:02 jmprieur

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

josephthomas5566 avatar Feb 02 '22 01:02 josephthomas5566

@josephthomas5566 : I transferred the issue to the right repo

jmprieur avatar Feb 02 '22 02:02 jmprieur

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

josephthomas5566 avatar Feb 02 '22 02:02 josephthomas5566

@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

jmprieur avatar Feb 02 '22 02:02 jmprieur

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 avatar Feb 02 '22 02:02 josephthomas5566

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

josephthomas5566 avatar Feb 02 '22 03:02 josephthomas5566

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.

Tratcher avatar Feb 02 '22 17:02 Tratcher

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

josephthomas5566 avatar Feb 02 '22 18:02 josephthomas5566

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;

Tratcher avatar Feb 02 '22 19:02 Tratcher

  • Yes.. Correct this is the expected one. But actual is what is getting reconstructed and hence custom redirect_URI

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

  • How do get the expected values without passing the entire redirect_uri..

  • I enabled the log ..with For| Proto| host ..Also retained the redirect_uri. Not sure what i am looking for ..but found the following values..

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 avatar Feb 02 '22 19:02 josephthomas5566

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?

josephthomas5566 avatar Feb 02 '22 23:02 josephthomas5566

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();
});

Tratcher avatar Feb 02 '22 23:02 Tratcher

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

  • Removed custom redirect_uri and added the below code..
  • Added new line app.Use((context, next) => { context.Request.Host = new HostString("product.portal.example.org"); return next(); });

With this code do you still need forwarders?

josephthomas5566 avatar Feb 03 '22 02:02 josephthomas5566

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.

Tratcher avatar Feb 03 '22 19:02 Tratcher

Are you unblocked on this @josephthomas5566 ?

jennyf19 avatar Mar 31 '22 17:03 jennyf19