This took some time to figure out, but I believe I have found the correct solution. It's funny because I already had the solution, I just didn't explore the options available in the scope.
CookieAuthenticationOptions
provides only one hook, a delegate property OnValidateIdentity
.
The OnValidateIdentity
occurs each time someone logs in(via cookie auth provider), which happens to be the perfect time to run some custom logic that determines their new Expiration time.
It also lets you configure it globally.
I've debugged it and can confirm the value for the configuration option sticks, and remains for future logins globally until app pool recycle. And also that the value override for expiration applies to the authenticated user after signin callback.
So it's up to you what logic determines the value, and whether you want to set that on a per-person level, or global level.
Here is the code sample to help paint a better picture of this..
public void ConfigureAuth(IAppBuilder app) {
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationGroupManager>(ApplicationGroupManager.Create);
app.CreatePerOwinContext<ApplicationDepartmentManager>(ApplicationDepartmentManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Login"),
Provider = new CookieAuthenticationProvider {
OnValidateIdentity = delegate(CookieValidateIdentityContext context) {
var stampValidator = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
validateInterval: TimeSpan.FromMinutes(15),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (id.GetUserId<int>())
);
Task result = stampValidator.Invoke(context);
bool found_custom_expiration = false;
int timeout = STATIC_CONFIG.DefaultSessionExpireMinutes;
DBHelper.Query(delegate (SqlCommand cmd) {
cmd.CommandText = @"
SELECT [value]
FROM [dbo].[Vars]
WHERE [FK_Department] = (
SELECT [Id] FROM [dbo].[ApplicationDepartments] WHERE [title] = 'Default'
)
AND [name]='Session Timeout'
";
return cmd;
}, delegate (SqlDataReader reader) {
timeout = reader["value"].ToInt32();
found_custom_expiration = true;
return false;
});
if (found_custom_expiration) {
// set it at GLOBAL level for all users.
context.Options.ExpireTimeSpan = TimeSpan.FromMinutes(timeout);
// set it for the current user only.
context.Properties.ExpiresUtc = context.Properties.IssuedUtc.Value.AddMinutes(timeout);
}
// store it in a claim, so we can grab the remaining time later.
// credit: https://stackoverflow.com/questions/23090706/how-to-know-when-owin-cookie-will-expire
var expireUtc = context.Properties.ExpiresUtc;
var claimType = "ExpireUtc";
var identity = context.Identity;
if (identity.HasClaim(c => c.Type == claimType)) {
var existingClaim = identity.FindFirst(claimType);
identity.RemoveClaim(existingClaim);
}
var newClaim = new System.Security.Claims.Claim(claimType, expireUtc.Value.UtcTicks.ToString());
context.Identity.AddClaim(newClaim);
return result;
}
},
SlidingExpiration = true,
// here's the default global config which was seemingly unchangeable..
ExpireTimeSpan = TimeSpan.FromMinutes(STATIC_CONFIG.DefaultSessionExpireMinutes)
});
ApplicationHandler.OwinStartupCompleted();
}
That could definitely be improved to issue the change only when not already changed. And you don't have to use a database query, it could be an XML read, or web.config appsetting, or whatever.
The identity is in place... So you could even access the context.Identity
and perform some kind of custom check on a ApplicationUser
.
So... the relevant bit...
OnValidateIdentity = delegate(CookieValidateIdentityContext context) {
var stampValidator = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (id.GetUserId<int>())
);
Task result = stampValidator.Invoke(context);
int my_custom_minutes = 60; // run your logic here.
// set it at GLOBAL level for all (future) users.
context.Options.ExpireTimeSpan = TimeSpan.FromMinutes( my_custom_minutes );
// set it for the current user only.
context.Properties.ExpiresUtc = context.Properties.IssuedUtc.Value.AddMinutes( my_custom_minutes );
return result;
}