CookieAuthentication cookies are invalid after application pool recycle - c#

I have a .net core project using Microsoft.AspNetCore.Authentication (2.2.0) configured to use CookieAuthentication. Cookies are configured to be persistent and expire after seven days. The problem is that all logged-in users are logged out whenever the application pool is recycled.
I am not using sessions at all. I have verified that the cookie is still present in the web browser, it seems like the existing cookies are determined to be invalid by the server. How can I change this behavior?
This is the current configuration:
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(
CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.AccessDeniedPath = "/";
options.LoginPath = "/";
options.LogoutPath = "/Authentication/Logout";
options.Events.OnRedirectToLogin = context =>
{
context.Response.Redirect("/?returnUrl=" + context.Request.GetEncodedPathAndQuery());
return Task.CompletedTask;
});

The error was an error in the configuration of the application pool in IIS. In the the "Advanced settings" of the application pool the setting "Load User Profile" needs to be set to "true".

Related

.Net Core 3.1 - Google Auth Cookie Not Persisting

I recently upgraded my ASP.Net Core MVC web app to .Net Core 3.1, and ever since, the Google External login cookie hasn't persisted properly as it did before the upgrade. The code itself didn't change at all, the app was just upgraded from .Net Core 2.0 to .Net Core 3.1.
The login flow works (user clicks login button, Google login screen appears, user logs in with Google Credentials/selects existing Google user and is sent back to the web app), but at random times (sometimes 30-60 seconds after login), the user's session randomly ends and the user is redirected back to the login screen. I've tested this extensively on localhost and the issue does not occur, but happens frequently on the hosting service I'm using (MochaHost).
Has anyone else had this issue with .Net Core 3.1?
The ConfigureServices method used at startup is below for reference.
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging();
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.EnableForHttps = true;
});
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration["ConnectionStrings:MySQL"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserStore<UserStore>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddGoogle(options =>
{
options.ClientId = Configuration["Authentication:Google:ClientId"];
options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
options.CallbackPath = new PathString("/signin-google");
});
services.ConfigureApplicationCookie(options =>
{
options.AccessDeniedPath = "/error/401";
options.Cookie.Name = "MyApp";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(180);
options.LoginPath = "/login";
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.SlidingExpiration = true;
});
services.AddMvc(option => option.EnableEndpointRouting = false);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IFileProvider>(new PhysicalFileProvider(Configuration.GetValue<string>("RootFilePath")));
var serviceAccountFilePath = GetGoogleServiceAccountCredentialPath();
var googleCredential = GoogleCredential.FromFile(serviceAccountFilePath);
services.AddSingleton(StorageClient.Create(googleCredential));
}
In my case, the issue was that Data Protection key storage was not available via user profile or HKLM registry after the .Net Core upgrade. I'm not sure if this is related to having to switch to 64-bit app pool mode after the upgrade to .Net Core 3.1 or not, but the bottom line is data protection keys were only being stored in memory. After shifting from server to server via the load balancer, the user keys were lost, causing a redirect back to the login page.
Sample Log entries:
2020-11-20 09:00:21.162 -08:00 [INF] Starting web host
2020-11-20 09:00:21.572 -08:00 [WRN] Using an in-memory repository. Keys will not be persisted to storage.
2020-11-20 09:00:21.574 -08:00 [WRN] Neither user profile nor HKLM registry available. Using an ephemeral key repository. Protected data will be unavailable when application exits.
I had to add the below code to use a folder on the hosting service to store key files so the user's session would persist:
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(Configuration.GetValue<string>("KeyStorePath")));

Shared cookie authentication not authenticating on server

When deploying two applications (one .net 4.6 the other .net core 2.2) that share an authentication cookie to a web farm environment, the "receiving" app does not authenticate. We have a very large web forms application that we are trying to eventually migrate to .net core, so for now we are handling the authentication in the web forms app and trying to share that authentication with the .net core app. We have upgraded the authentication in the web forms app to OWIN/Katana cookie based authentication. The apps are deployed on the same servers under the same site (server.com/app1 server.com/app2). Everything works fine locally, you sign in on one and move to the other and you are still logged in. When we deploy to our servers, which are load balanced, the .net core app receives the cookie, but isAuthenticated is false.
I have been able to manually decrypt the cookie in the .net core app and it is able to print out the claims contained within it, but the IsAuthenticated flag is still false. I've tried changing the cookie domain, cookie path, security policy, and authentication type with no success.
Web Forms app Startup.cs:
var provider = DataProtectionProvider.Create(new DirectoryInfo(System.Configuration.ConfigurationManager.AppSettings["KeyRingLocation"]),
(builder) => {
builder.SetApplicationName("sharedApp");
builder.PersistKeysToFileSystem(new DirectoryInfo(System.Configuration.ConfigurationManager.AppSettings["KeyRingLocation"]));
});
IDataProtector protector = provider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
"Identity.Application",
"v2");
app.UseCookieAuthentication(new Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions
{
CookieName = ".AspNet.SharedCookie",
LoginPath = new PathString("/Login.aspx"),
CookiePath = "/",
AuthenticationType = "Identity.Application",
CookieSecure = Microsoft.Owin.Security.Cookies.CookieSecureOption.Always,
CookieDomain = System.Configuration.ConfigurationManager.AppSettings["CookieDomain"],
TicketDataFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector)),
CookieManager = new ChunkingCookieManager()
});
.net core app Startup.cs:
services.AddDataProtection()
.SetApplicationName("sharedApp")
.PersistKeysToFileSystem(new DirectoryInfo(Configuration.GetSection("KeyRingLocation").Value));
services.AddAuthentication("Identity.Application")
.AddCookie("Identity.Application", options =>
{
options.Cookie.Name = ".AspNet.SharedCookie";
options.Cookie.Domain = Configuration.GetSection("CookieDomain").Value;
options.Cookie.Path = "/";
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
web forms login code:
...
var auth = Context.GetOwinContext().Authentication;
var identity = new ClaimsIdentity("Identity.Application");
identity.AddClaim(new Claim(ClaimTypes.Name, profile.UserName));
...
auth.SignIn(identity);
There are no errors being thrown so it is really hard to figure out what the issue is. I would expect it to respect the authentication cookie as it does running locally, but the user identity is null and isAuthenticated is false.

Cookie based authentication on multiple 'localhost' Asp.Net Core Sites

Scenario is only an issue on a development machine.
I have multiple different and entirely independent ASP.NET Core 2.2 projects running on my dev machine under "localhost".
Once I successfully authenticate and log in on one project, I can no longer log in to the other projects. I'm assuming it's something to do with the auth cookies.
All projects have the same identical and basic Identity authentication.
services.AddAuthentication();
services.ConfigureApplicationCookie(opt =>
{
opt.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.None;
opt.Cookie.HttpOnly = true;
opt.Cookie.Expiration = TimeSpan.FromHours(4);
opt.ExpireTimeSpan = TimeSpan.FromHours(4);
});
The sign-in call is succeeding:
result = await _signInManager.PasswordSignInAsync(portal.ID, model.Username, model.Password, model.RememberMe, true, loggedInUser);
However, once the user is redirected to the home page, which required authentication, I see the following in the debug output:
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Information: Authorization failed.
... and the user is kicked back to the login page.
Is there a way around this issue?
I would consider running your sites under different domains, and then mapping the domains to localhost (or rather, 127.0.0.1) in your hosts file.
This question explains the technique: Add subdomain to localhost URL
Here is an article which explains the hosts file for different operating systems: https://www.howtogeek.com/howto/27350/beginner-geek-how-to-edit-your-hosts-file/
Problem solved.
services.ConfigureApplicationCookie(opt =>
{
opt.Cookie.Name = "Unique Value Per App";
});
All I have to do is post on SO and the problem solves itself!

Microsoft authentication automatically signing in account. Causing "Correlation failed" error when user manually selects account

We've been leveraging Cookie authentication within our .NET Core 2.2 application & logging users in after verifying their identity via Microsoft external 3rd party provider. Just recently, we've observed an error in this workflow which states:
Microsoft.AspNetCore.Authentication.MicrosoftAccount.MicrosoftAccountHandler:Information: Error from RemoteAuthentication: Correlation failed.."
We've narrowed it down to the scenario where if a user is already logged in to their Microsoft account (only one account logged in/active)... and if they then log in to our website they are automatically signed in without having to select their account name or any other interaction. What's more, if the user DOES click on their account name, this fires off another sign in request & I believe is resulting in the "Correlation failed.." error due to the request/response cookies being in conflict.
I am able to reproduce the issue from localhost, though our actual hosting provider is within Azure (App Services).
I've already explored some of the solutions/approaches mentioned within the following Microsoft docs which mention configuration for load balancers and proxy servers (Forwarding Headers)... as well as enforcing HTTPS in ASP.NET Core:
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1
https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-2.1&tabs=visual-studio
These do not appear to address the root of the issue which I understand to be the automatic sign-on/selection of the user account when the security Challenge is made to the Microsoft provider.
Here is a snippet from our Startup.cs service configuration for the 3rd party authentication config.
// Authentication is added via Cookie
services.ConfigureApplicationCookie(options => options.LoginPath = "/Login");
services.AddAuthentication(opts =>
{
opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opts.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(opts =>
{
opts.Cookie.SecurePolicy = CookieSecurePolicy.Always;
opts.LoginPath = "/auth/login";
opts.LogoutPath = "/auth/logout";
opts.ClaimsIssuer = "<ISSUER_HERE>"; // *** redacted for privacy
})
.AddMicrosoftAccount(options =>
{
options.ClientId = Configuration["Authentication:ApplicationId"];
options.ClientSecret = Configuration["Authentication:Password"];
options.Events.OnRemoteFailure = ctx =>
{
// React to the error here. See the notes below.
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
ctx.HandleResponse();
return Task.FromResult(0);
};
});
Ideally, the "automatic sign-on" behavior is prevented so the user must select their account from the Microsoft account selection prompt (even if they've previously signed in).
For now, we're redirecting the user to an error page, where we can clear all cookies and have the user re-attempt the login. This poses a problem if they continually select their user account during the login process & both requests are conflicting.
Any insight would be greatly appreciated!
Alrighty then, I got it.
It seems a bit hacky, but hey, it works.
.AddMicrosoftAccount(options =>
{
// Your configuration here
options.Events.OnRedirectToAuthorizationEndpoint = context =>
{
context.HttpContext.Response.Redirect(context.RedirectUri + "&prompt=select_account");
return Task.FromResult(0);
};
})

Cookie Authentication expiring too soon in ASP.NET Core

I have a ASP.NET Core 1.1.2 project in which I am using cookie authentication. I am having a problem where users are being prompted to log back in after being idle for an hour or less, and losing work. The code below is what I'm using in the Configure function in Startup.cs to set this up and from what I can tell, it should expire after at least 8 hours. BTW, ProjectProcessFlow is just the name of the project.
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "ProjectProcessFlow",
LoginPath = new PathString("/Account/Login/"),
ExpireTimeSpan = new TimeSpan(8, 0, 0),
SlidingExpiration = true,
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
I am including Microsoft.AspNetCore.Authentication.Cookies v1.1.2 in NuGet. What do I need to do to get the login expiration to happen at the expected time?
Additional Information:
I found that when the timeout happened and the user was asked to login again, a warning was recorded in the Event Viewer on the server that it couldn't find the logs folder under the project. So I created that folder, and waited for the timeout to happen again. When that happened, a log file was created in that folder that contained this:
Hosting environment: Production
Content root path: C:\inetpub\wwwroot\Sprout
Now listening on: http://localhost:13423
Application started. Press Ctrl+C to shut down.
When I repeated this process, the same thing happened, except that a different number appeared after "localhost:". I should mention that the project name is ProjectProcessFlow, but the URL ends in Sprout.
I know that is too late for answering this question, but for whom facing this.
The IIS reset pool every 20 minutes and every 20 mins ASP.NET generate new key for protect cookie values (Authentication and Session). to prevent this, add following code to ConfigureServices in Startup class
services.AddDataProtection()
.PersistKeysToFileSystem(new System.IO.DirectoryInfo("SOME WHERE IN STORAGE"))
//.ProtectKeysWithCertificate(new X509Certificate2());
.SetDefaultKeyLifetime(TimeSpan.FromDays(90));
A complete guide is here. It is all about DataProtection
users are being prompted to log back in after being idle for an hour
or less, and loosing work.
I have similar configuration, but it works fine for me.
One thing I can think of is you cannot let web server idle for 20 minutes. IIS's app pool default idle time-out is 20 minutes (I could not say for other Linux web server).
So you could either set longer app pool time-out (0 for infinity), or ping every 5 minutes from external service like Monitis.
Do you have services.AddIdentity set up in your ConfigureServices method?
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
// Require a confirmed email in order to log in
config.SignIn.RequireConfirmedEmail = true;
// Cookie settings
config.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromHours(10);
config.Cookies.ApplicationCookie.LoginPath = "/Account/LogIn";
config.Cookies.ApplicationCookie.LogoutPath = "/Account/LogOut";
}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
I had a similar issue and resolved it here
ASP.NET MVC Core Identity & Cookies

Categories