ASP.NET Core app under IIS - can't stay logged in - asp.net-core

I am nearly finished migrating an ASP.NET Core app from RC1 to 1.1.0. Everything is working great until I try to publish the app and host it in IIS.
In a debugger, or working directly from Kestrel, I have no problems. I can reach my site, login, and interact with it normally.
Under IIS, however, I can load the site and am correctly redirected to my login page, but despite logging in with the correct credentials, I am redirected back to the home page and am not logged in. I can repeat this cycle over and over - I am authenticating, but some part of the IIS/Core middleware isn't keeping me logged in.
My site is using ASP.NET Core Identity which is largely unchanged from the RC1 default VS project template.
AccountController:
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
var signInStatus = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, false);
if (signInStatus.Succeeded)
return RedirectToLocal(returnUrl);
ModelState.AddModelError("", "Invalid username or password.");
return View(model);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Unfortunately, I can't debug this, because the VS debugger hooks Kestrel, and when hosting natively from Kestrel this problem doesn't exist. I'm not able to attach my debugger to the running Kestrel process (that was launched by IIS) - I can get attached but my breakpoints will not attach.
I am certain that the method above is being called. If I enter an incorrect password, I do indeed see the "Invalid username and password" prompt. When I login "successfully", I am redirected to the original page. The problem is that after the redirect, I'm not logged in - the navbar still shows my Login button instead of the Logout button, and I can't interact with any authorized controllers.
I know this is a super vague question with little source, but I'm not sure where to even start with this one. I'm hoping someone might see what my problem is and point me in the right direction, or at least be able to suggest some steps I can take to isolate and try to debug the issue. Or, barring that, even some hints as to what information I can add to my question to give necessary details.
Thank you!

Two starting points you can try:
Install the latest Windows Server Hosting bundle on your server.
Monitor and compare traffic between the working and broken systems after you click the login button. Are you getting the Identity authentication cookie returned on the broken system similar to the working one?

Related

Suspected bug in Microsoft Identity Platform with ASP.NET Core 3.1 Razor Pages

I am developing an application to be hosted in the Azure App Services environment which consists of a front-end Web App, a back-end Web API and a SQL Database (using Azure SQL). The front-end Web App is a Razor Pages app. We are trying to use the Microsoft Identity Platform (via Microsoft.Identity.Web and Microsoft.Identity.Web.UI libraries) to acquire an access token for the API when needed.
It works perfectly well the first time, but once a token has been acquired and cached - if the application is restarted it fails with this error:
IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
No account or login hint was passed to the AcquireTokenSilent call.
Startup configuration is (I've tried various variants of this):
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.HandleSameSiteCookieCompatibility();
});
services.AddOptions();
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { Configuration["Api:Scopes"] })
.AddInMemoryTokenCaches();
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddRazorPages().AddRazorRuntimeCompilation().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddMvc();
//Other stuff...
}
I have tried for many days trying to find either a resolution workaround for this. I can catch the
error, but there is no action we can take programmatically that seems to clear the problem (the ITokenAcquisition interface does not offer the option to force an interactive login).
I have found that it is ONLY a problem in a Razor Pages application - a controller-based MVC Web App with almost identical startup code does not exhibit the problem.
I have also found that, by creating a controller-based test MVC Web App and configuring it with the same client id, tenant id etc. as the app we're having problems with, then starting it up (within the Visual Studio development environment) as soon as the main app gets the problem, I can clear the error condition reliably every time. However this is obviously not a viable long-term solution.
I have searched for this problem on every major technical forum and seen a number of similar sorts of issues raised, but none provides a solution to this precise problem.
To replicate:
Create an ASP.NET Core 3.1 Web API.
Create an ASP.NET Core 3.1 Razor Pages Web App that calls the API.
Register both with Azure Active Directory and configure the App to request a token to access the API (as per various MS documents).
Run - if everything is set up correctly the login screen will appear and all will work correctly.
Stop the Web App, wait a couple of minutes and re-start. The error above will now appear.
I have raised a Microsoft support request for it - has anybody else come across this and found a solution for it?
I have finally got to the bottom of this, largely thanks to this: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/216#issuecomment-560150172
To summarise - for anyone else having this issue:
On the first invocation of the web app you are not signed in, and so get redirected to the Microsoft Identity Platform login, which logs you in and issues an access token.
The access token is stored in the In-Memory token cache through the callback.
All then works as expected because the token is in the cache.
When you stop, and then re-start the web app within a reasonably short time, it uses the authentication cookies to pick up the still-current login, and so it does not access the Identity Platform and you do NOT get an access token.
When you ask for a token the cache is empty - so it throws the MsalUiRequiredException.
What isn't really made clear in any of the documentation is that this is supposed to happen - and that exception is picked up by the "AuthorizeForScopes" attribute but only if you allow the exception to fall all the way through and don't try to handle it.
The other issue is that in a Razor Pages app the normal AuthorizeForScopes attribute has to go above the model class definition for every page - and if you miss one it may trigger the above problem.
The solution proposed by "jasonshave" in the linked article solves that problem by replacing the attribute with a filter - so it will apply to all pages.
Maybe I'm a bit old-school, but the idea of using an unhandled exception as part of a planned program control flow doesn't sit right with me - at the very least it should be made clear that that's the intention. Anyway - problem now solved.

Using Azure Active Directory with aspnetcore app

I am trying to get up to speed using Azure Active Directory with an aspnetcore 5.0 web app.
I have followed this tutorial, and it seems to work fine: That is, I start the app, and it then shows the Active Directory Login, and I log in with my Microsoft Account.
I then try to view the app as a logout out user. So I log out. However, when I do so, I am directed back to the Active Directory login again. It seems there is no way to view the app unless I am logged in.
Instead, I would like to see the login prompt only when I click the Login link. And I should be able to view the app after logging out.
How do I do this?
One way is to use custom URL Rewriting Middleware to redirect by checking the Homepage path.
For Example, In app.UseMvc use below code:
app.UseRewriter(
new RewriteOptions().Add(
context => { if (context.HttpContext.Request.Path == "/AzureAD/Account/SignedOut")
{ context.HttpContext.Response.Redirect("/Home/Index"); }
})
);

Razor Pages - HTTP Error 400 (Invalid Anti Forgery Token) when idle the web site

I'm developing a web site in Blazor Server Side. My Login page is a Razor Page and everything works as expected, but if a user leaves their browser open for a period of time around 20 minutes and then performs the login they get an Http Error 400. I think it is for the Anti Forgery Token, because if you delete the cookie ".AspNetCore.Antiforgery" you got the same error. What should I do to solve this issue? What do you recommend?
If the application is being hosted in IIS I recommend setting Load User Profile = True in the application's app pool > Advanced settings menu. I had a similar issue in the past and I noticed every time the application restarted in IIS the keys were not persisted and any form opened before the restart is useless. However as soon as I changed the setting the key persisted. https://codeshorts.com/ASP-NET-Core-IIS-Invalid-Anti-Forgery-Token-Error-400
This also seems to have been an issue with Azure hosted apps
https://stackoverflow.com/a/52302702/1843966
You can try to apply IgnoreAntiforgeryToken Attribute to LoginModel class to skip antiforgery token validation, like below.
[AllowAnonymous]
[IgnoreAntiforgeryToken]
public class LoginModel : PageModel
{
//...
Note: normally, disabling antiforgery token validation is not recommended. But in this thread, applying it to LoginModel (user login functionality) should be fine.
I found this approach using code so you can catch the exception if it does fail

MVC AntiForgeryToken cookie missing in browser before login.

Application is accessed under https:// url.
In MVC application we have added ##Html.AntiForgeryToken() on cshtml razor engine and ValidateAntiForgeryToken on controller.
It all worked fine for after login pages, but with login page it goes to application error as there was no cookie seen on browser before logging into the system.
Went through several forums, articles and even added the following piece of code in global.asax.
AntiForgeryConfig.CookieName = "CSRF";
AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
//AntiForgeryConfig.RequireSsl = true; -- only for https.
Even wrote a simple SetCookie in get method of login page, that cookie doesn't show up on browser. What could be the problem.
Why i was able to see the below in cshtml but controller doesn't accept the post with antiforgeryvalidation before login.
.
Any help would be greatly appreciated

Following ExternalLoginCallback redirection fails without exception

I am using a modified Internet application template for MVC4 and implemented the AuthConfig details for Twitter authentication. Following a redirect from twitter, and successful login, I get a redirect to the below URL and the webapp just dies with a Page Cannot Be Displayed error in IE with no further details:
provider=twitter&sid=XXX&oauth_token=XXX&oauth_verifier=XXX">http://domain.com/Account/ExternalLoginCallback?provider=twitter&sid=XXX&oauth_token=XXX&oauth_verifier=XXX
ExternalLoginCallback function as implemented in the template works as expected and I see a call to the local redirect which calls Home/Index. However rather than this view being rendered I see nothing happening on the site. I've used the network tracing functionality in IE to see the request details but that didn't really help me understand the issue even more.
I'm aware this is a little vague but I'd appreciate it if you let me know what I can check additionally.
I am stupid.
This is what I had in the Home controller:
if (Request.IsAuthenticated)
return RedirectToAction("Index", "Home");
else
return View();
and it was supposed to be:
if (!Request.IsAuthenticated)
return RedirectToAction("Index", "LandingPage");
else
return View();
I am sorry I asked this question but I hope there is someone else out there as stupid as I am and they may benefit from this.