microsoft-identity-web
microsoft-identity-web copied to clipboard
Authentication Issue for external URL ( Power BI , .net Core 3.1, Azure AD App,IIS)
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 : 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 ?
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
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, do you use Microsoft.Identity.Web?
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 : I transferred the issue to the right repo
Thanks .. hopefully someone from this team can help us here...
@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
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();
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
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.
I have removed the conflict in the latest iteration.. Can you let me know how to
- enable ForwardedHeaders.XForwardedHost
- 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
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;
- 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
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?
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();
});
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?
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.
Are you unblocked on this @josephthomas5566 ?