Microsoft.Identity.Web.UI works locally but not in App Service - asp.net-core

I've been trying to add auth to my web app following: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/1-WebApp-OIDC/1-1-MyOrg
If I run locally with appsettings.Development.json via dotnet run, I can log in using my organization credentials as expected. When I containerize and deploy to my Web App in Azure, I do not get logged in successfully. The url in the browser stays at /signin-oidc and goes to the Error page from the default Razor pages app.
The App Service logs have messages saying .AspNetCore.Correlation.OpenIdConnect.[key?] cookie not found
[Update] The auth flow works on my phone but not on desktop.
Why would the same code work locally but not deployed?
Why isn't the cookie found?
Why does it work on iOS but not Windows?

Try to remove app.UseCookiePolicy(); in startup class.
Take a look here
Or the problem was IdentityServer was still using AddDeveloperSigningCredential
You can add a certificate in your code it will work perfectly
Refer here for more info

tl;dr - Setting the same-site cookie options in the authentication settings fixed this.
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
Configuration.Bind("AzureAd", options);
options.NonceCookie.SameSite = SameSiteMode.Unspecified;
options.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
});
This seems to be because the App Service enforces TLS-only and handles the TLS termination. Because of this, requests to the application are always HTTP even though the browser URL is HTTPS. This causes a number of problems, the first was that the redirect URL never matched since the Identity library uses the request scheme to form the redirect URL. I had "fixed" this by injecting a redirect URL rewrite:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
Configuration.Bind("AzureAd", options);
options.Events ??= new OpenIdConnectEvents();
options.Events.OnRedirectToIdentityProvider += _fixRedirect;
});
...
private async Task _fixRedirect(RedirectContext context)
{
context.Request.Scheme = "https";
if(!context.ProtocolMessage.RedirectUri.StartsWith("https"))
context.ProtocolMessage.RedirectUri = context.ProtocolMessage.RedirectUri.Replace("http", "https");
await Task.CompletedTask;
}
However, the correlation cookie also seems to use the same HTTP request so that when the redirect comes back, the HTTPS doesn't match. Relaxing the SameSiteMode explicitly tells the browser(?) to allow the differing schemes. This is why my phone, with a different browser with different cookie policy, worked while desktop did not. Running locally using the dev-certs allowed the schemes to match since the app was terminating the TLS rather than the App Service.
I hooked into the CookiePolicyOptions.OnAppendCookie event to inspect the cookies while debugging to find this out.

Related

ASP.Net Identity Login Redirect Enforce Protocol (Https) Part 2 (.Net 6++)

Prior reference (.Net Framework/ASP.Net MVC): ASP.Net Identity Login Redirect Enforce Protocol (Https)
It seems this is still an "issue" in .Net 6+. There are cases where the return url constructed by the infrastructure results in an http scheme/protocol instead of https for oauth/external logins (Google, etc). This obviously fails because it must be https.
While I haven't gone deep into things, because I haven't found the source code for it (yet?), it's likely the same "issue" - at the app level, it doesn't "see" a https request (because SSL is offloaded somewhere) and therefore the url created "matches" the scheme/protocol, resulting in an http redirect url.
End of day, whatever hosting infrastrucutre/configuration my host has is in place is beyond my control. Therefore, the ultimate goal is to force https (hard code, skip/override whatever scheme/protocol check/eval in place).
There's nothing special in my setup and it's working fine in local/dev (https) testing. It's only when the application is finally hosted (production):
In startup program.cs this is the only related code I have for external login (along with the scaffolding/templates of the identity package):
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<my_db_context>();
builder.Services.AddAuthentication().AddGoogle(goog =>
{
goog.ClientId = builder.Configuration["GoogleAuthClientId"];
goog.ClientSecret = builder.Configuration["GoogleAuthClientSecret"];
});
The issue:
the origin is https
the redirect uri sent to Google Auth is http - this will always fail
Can anyone point me to relevant docs/source on how to add/override options in .Net 6 and above? (similar to prior implementations in .Net Framework/MVC)?
The answer is in the comment by #Tratcher:
Official Ref: Configure ASP.NET Core to work with proxy servers and load balancers
Essentially: ForwardedHeadersMiddleware
For my specific case:
In some cases, it might not be possible to add forwarded headers to
the requests proxied to the app. If the proxy is enforcing that all
public external requests are HTTPS, the scheme can be manually set
before using any type of middleware:
...
app.Use((context, next) => {
context.Request.Scheme = "https";
return next(context); });
...

Identifying an authenticated user in back-end page requested via window.open

We use IdentityServer4 in our .NET solution, which also includes AspNetCore Web API and Angular front-end app.
There are standard middleware settings to setup identity like app.UseAuthentication() / app.UseAuthorization() / etc. (it's actually an ABP framework-based solution). As a result of these settings, all authentication tokens (access_token, refresh_token, etc.) are stored in Local Storage (I have not found where exactly I can select between Local Storage and other kinds, BTW).
Anyway, it has worked somehow in our DEV environment.
But suddenly the need to use window.open from Angular app popped up. This page is a Hangfire dashboard. Which accesses other resources related to dashboard functionality. And it caused a lot of headache: now, to identify user in server page called from window.open we need to use cookies (URL is not considered of course).
does it mean we have to switch fully from Local Storage to Cookies for storing tokens?
how and where to set it up? Or, if it's not too wild and senseless to just copy existing access_token to Cookies - when and where to do that? My idea was to copy access_token when it is created in Local Storage to Cookies and delete when a user logs off and probably under bunch of different conditions, like browser window is closed, etc. (which ones exactly?)
probably to set refresh_token to be stored in Cookies and read it in the mentioned server page, then obtain access_token by it (if it makes sense at all)
UPDATE: i've finally came up with the following, but it does not work. The idea is: when Angular app makes request to back-end and authentication token is already present - the token needs to be saved to cookies. I see the cookies is added. But later on - on next request - it falls into the condition again, because actually the given cookie is not saved:
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
if (context.SecurityToken is JwtSecurityToken accessToken && !context.HttpContext.Request.Cookies.ContainsKey(accessTokenCookieName))
{
context.HttpContext.Response.Cookies.Append(
accessTokenCookieName,
accessToken.RawData,
new CookieOptions
{
Domain = context.HttpContext.Request.Host.Host,
Path = "/",
HttpOnly = true,
SameSite = SameSiteMode.Strict,
Secure = true,
MaxAge = TimeSpan.FromMinutes(60)
});
}
return Task.CompletedTask;
},
...
}
All apps are web application sharing the same domain. And CORS is set up. AllowCredentials() is called as well when setting up middleware.

Asp Net Core with Identity not working after removing HTTPS

I was trying to remove HTTPS to test some caching features and my authentication stopped working. I read that when using Identity authentication will stop working without HTTP, even a custom authentication cookie with an authentication scheme won't work either.
After I comment these 2 lines my app won't work anymore.
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
The use of HTTPS is not mandatory, the application is to be used on our intranet and I have used Identity just to manage users. What options do I have right now?
even with all this, when I try to login it redirects me back to login page, and this using a custom authentication cookie not indentity.
services.AddAuthentication().AddCookie(AuthenticationSchemes.Production, options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(8);
options.LoginPath = new PathString("/Login");
options.LogoutPath = new PathString("/Logout");
options.Cookie.HttpOnly = true;
options.AccessDeniedPath = new PathString("/AccessDenied");
options.SlidingExpiration = true;
options.Cookie.Name = "NoPaper.Production";
options.ExpireTimeSpan = TimeSpan.FromHours(8);
});
UPDATE
It seems the only solution that worked was to create a new project without https and just copy everything from the other and install nuget packages and it worked.
Please explain more detail about the Authentication not working, is there any error? Try to use F12 developer tools to check it. And please make sure you are not using the ex
As far as I know, when we create dotnet core application via Visual Studio, if we select the Individual User Accounts(using Identity), it will generally force the usage of https for best practice reasons.
In this scenario, to disable HTTPS, we could refer to the following steps:
remove the UseHttpsRedirection from the Startup.cs:
app.UseHsts();
app.UseHttpsRedirection();
Right click the project, and click the Properties, in the Debug tab, unchecked the Enable SSL option. Then, the application will be launched using HTTP requests.
If you are not using the Visual Studio, you could also removing the SSL references in the launchSettings.json file:
Besides, please make sure you are not using the external authentication services in your application. I have created new dotnet core application and use above method to disable HTTPS, and then, I could use Identity to register a new user and login.

Azure AD Redirect URL Using Application Gateway

We have an ASP Core 2.0 App working nicely with Azure AD on the private network. However, we've been playing around with the Azure Application Gateway, investigating the possibility of allowing access to the app from outside for remote workers etc.
We have registered the app on the Gateway, and, once logged in with Azure AD, the anonymous front page is accessible via ourapp.msappproxy.net. However, when signing in (again?) in the app, the client is redirected back to intervalServer/signin-oidc which fails as it is not accessible externally.
While I doubt this is any part of the solution, I have tried overriding the redirect "CallbackPath": "/signin-oidc", to absolute path ourapp.msappproxy.net/signin-oidc but I can't seem to work out how. Changing the reply URL in Azure Portal doesn't help either (although I doubted it would, this is just for verification right?).
I can't seem to find any guidance on this on this particular scenario, so that would be welcome. Otherwise, I'm left pondering the following:
1, If I could change the redirect to ourapp.msappproxy.net/signin-oidc, would that solve the sign in issue?
2, Do I even need an additional sign in step, or should I be changing the app to accept AzureAppProxyUserSessionCookie or AzureAppProxyAccessCookie? (If that's even an option?)
Thanks to rfcdejong in the comments for putting me on track. In our case I was able use Azure AD with the Azure Application Gateway by overriding OnRedirectToIdentityProvider event and supplying the proxy url in ConfigureServices
services.AddAuthentication(...)
.AddOpenIdConnect(options =>
{
options.ClientId = Configuration["Authentication:AzureAD:ClientId"];
options.Authority = Configuration["Authentication:AzureAd:Authority"];
options.CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"];
if (IsProduction) // So that I can use the original redirect to localhost in development
{
Task RedirectToIdentityProvider(RedirectContext ctx)
{
ctx.ProtocolMessage.RedirectUri = "https://ourapp.msappproxy.net/signin-oidc";
return Task.FromResult(0);
}
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = RedirectToIdentityProvider
};
}
})
The return URI needs to be configured to match for the app in Azure Portal.
Users also need to be assigned, but the internal app is now available anywhere without requiring direct access to the server.

XSRF-TOKEN not updated when using IISExpress and localhost

.Net Core api layer and .Net Core MVC w/ Angular2 front end. Locally, they are running in different website (localhost:xxx1 and localhost:xxx2) and published, the api is running in a sub directory of the frontend.
I've set up the .Net Core Antiforgery like so:
in the ConfigureServices section:
services.AddAntiforgery(options =>
{
options.HeaderName = "X-XSRF-TOKEN";
});
in the Configure section:
app.Use(next => context =>
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
return next(context);
});
When i initially launch the sites and browse to the website in Chrome, I do get the 2 Antiforgery tokens (.AspNetCore.Antiforgery.xxxx and the XSRF-TOKEN) and when I make a get/post/etc call I see the x-xsrf-token header is added to the call.
The problem is on each call, the api returns a new XSRF-TOKEN cookie but locally my cookie is not updated, it always contains the original value. When published online, this doesn't happen, the cookie updates every time.
I've tried setting the sites up locally to use localhost.somedomain.com but that didn't work.
Any suggestions as to what I'm doing wrong or how to get it working locally?
The issue on my local machine was that I was running each app in its own IIS Express (localhost:xxx1 and localhost:xxx2).
I ended up setting IIS up to host .net core following this article (https://learn.microsoft.com/en-us/aspnet/core/publishing/iis) and i'm getting the expected result.