Reputation: 33
Problem: I have an ASP.NET Core web application using cookie authentication. Users can choose to persist their login (1-year expiration) or have a session-based login (120 minutes expiration). However, we noticed that user sessions are not being persisted, and users are getting logged out unexpectedly—even before the minimum session timeout.
Upon investigating, I checked the authentication cookie in the browser, and it is still present when the auto-logout occurs. This suggests that the cookie is not being validated properly on the server.
Debugging steps taken:
Checked cookie expiration: the cookie is still present when auto-logout occurs.
Session validation issue: it appears that the session is not validating the cookie.
IIS idle timeout: IIS has an idle timeout setting, but that applies to the server being idle, not the user.
Enabled data protection persistence: to persist authentication across application pool restarts, I added:
var storagePath = configuration["PersistKeysToFileSystemPath"];
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(storagePath))
.SetApplicationName("QiranWebLatest");
After adding this, I saw that the application was generating a file in the specified directory.
Checked logs and found this error:
warn: Microsoft.AspNetCore.Session.SessionMiddleware[7]
Error unprotecting the session cookie.
System.Security.Cryptography.CryptographicException: The key {6bf436c7-5b03-4068-b973-4d49875bcca5} was not found in the key ring. For more information go to https://aka.ms/aspnet/dataprotectionwarningat Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData)
at Microsoft.AspNetCore.Session.CookieProtection.Unprotect(IDataProtector protector, String protectedText, ILogger logger)
Authentication setup in Program.cs
:
var authenticationBuilder = services.AddAuthentication(options =>
{
options.DefaultScheme = QiranAuthenticationDefaults.AuthenticationScheme; // Use cookies as default
options.DefaultSignInScheme = QiranAuthenticationDefaults.ExternalAuthenticationScheme;
})
.AddCookie(QiranAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.Name = $"{QiranCookieDefaults.Prefix}{QiranCookieDefaults.AuthenticationCookie}";
options.Cookie.HttpOnly = true;
options.LoginPath = QiranAuthenticationDefaults.LoginPath;
options.AccessDeniedPath = QiranAuthenticationDefaults.AccessDeniedPath;
options.SlidingExpiration = true;
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = async context =>
{
var userPrincipal = context.Principal;
if (userPrincipal == null)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(QiranAuthenticationDefaults.AuthenticationScheme);
return;
}
var lastValidationClaim = userPrincipal.FindFirst("LastValidation");
var lastValidationTime = lastValidationClaim == null
? DateTime.MinValue
: DateTime.Parse(lastValidationClaim.Value);
if ((DateTime.UtcNow - lastValidationTime).TotalMinutes < 30) // Revalidate every 30 minutes
{
return;
}
var username = userPrincipal.FindFirst(ClaimTypes.Name)?.Value;
if (string.IsNullOrEmpty(username))
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(QiranAuthenticationDefaults.AuthenticationScheme);
return;
}
using (var scope = context.HttpContext.RequestServices.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MData3Context>();
var user = await dbContext.Members.SingleOrDefaultAsync(m => m.UserName == username);
if (user == null || (user.Banned.HasValue && user.Banned.Value))
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(QiranAuthenticationDefaults.AuthenticationScheme);
return;
}
}
var identity = (ClaimsIdentity)userPrincipal.Identity;
if (lastValidationClaim != null)
{
identity.TryRemoveClaim(lastValidationClaim);
}
identity.AddClaim(new Claim("LastValidation", DateTime.UtcNow.ToString()));
context.ShouldRenew = true; // Refresh authentication ticket
}
};
});
Additional attempts to fix: enabled LoadUserProfile
in IIS, but the issue persists. I'm still getting redirected back to the login page after logging in.
Question: why is my authentication cookie not being validated properly, leading to unexpected logouts? How can I fix this issue so that the user session persists correctly?
Upvotes: 0
Views: 25