I have an Identity server 4 application with asp .net identity. I have the cookies set up to slide.
services.ConfigureApplicationCookie(opts =>
{
opts.Cookie.Expiration = TimeSpan.FromDays(30);
opts.SessionStore = new RedisCacheTicketStore(new RedisCacheOptions()
{
Configuration = configuration["Redis:HostPort"]
}, logger, configuration);
opts.Cookie.SameSite = SameSiteMode.None;
opts.SlidingExpiration = true;
opts.ExpireTimeSpan = TimeSpan.FromDays(30);
}
);
Not Sliding
Localhost: When the user logs in .AspNetCore.Idenitty.Application gets an expiration time. When the page is refreshed the expiration is updated i can see the timestamp change.
Production: However if i check this when its up on the server the user logs in and .AspNetCore.Idenitty.Application gets an expiration time with a time stamp of when the logged in. However when the page is refreshed the time stamp does not change. It remains the same as it was when the user logged in.
User kicked out after 30 minutes
Production: The second issue is that as you can see the expiration time is set for a month in advance yet when on the server in 30 minutes this user will be kicked out and forced to login again. I cant keep a user logged in for more then 30 minutes even if they are active.
Security stamp
I have checked the users security stamp has not changed and the token contains "AspNet.Identity.SecurityStamp": "[users actual key]"
Update
So after some digging i finally decided to over ride the security stamp validation. I did that by over riding the following methods in my ApplicationSignInManager
public override async Task<ApplicationUser> ValidateSecurityStampAsync(ClaimsPrincipal principal)
{
if (principal == null)
{
Logger.LogError(LoggingEvents.ApplicationSignInManagerSecurityTokenValidation, "ClaimsPrincipal is null");
return null;
}
var user = await UserManager.GetUserAsync(principal);
if (await ValidateSecurityStampAsync(user, principal.FindFirstValue(Options.ClaimsIdentity.SecurityStampClaimType)))
{
return user;
}
if(user == null)
Logger.LogError(LoggingEvents.ApplicationSignInManagerSecurityTokenValidation, "User not found [principal {principal}]", principal);
var principalSecurityStamp = principal.FindFirstValue(Options.ClaimsIdentity.SecurityStampClaimType); // Security stamp from claims
var userManagerSecurityStamp = user.SecurityStamp; // Security Stamp from usermanager
var getSecurityStampAsyncResults = await UserManager.GetSecurityStampAsync(user); // Security stamp from GetSecurityStampAsync
Logger.LogError(LoggingEvents.ApplicationSignInManagerSecurityTokenValidation,
"Security stamp Validation Failed: [principalSecurityStamp {principalSecurityStamp}] != [getSecurityStampAsyncResults {getSecurityStampAsyncResults}] also ([userManagerSecurityStamp {userManagerSecurityStamp}] )", principalSecurityStamp, getSecurityStampAsyncResults, userManagerSecurityStamp);
return null;
}
public virtual async Task<bool> ValidateSecurityStampAsync(ApplicationUser user, string securityStamp)
=> user != null &&
// Only validate the security stamp if the store supports it
(!UserManager.SupportsUserSecurityStamp || securityStamp == await UserManager.GetSecurityStampAsync(user));
This resulted in some very interesting information showing up in my log instantly.
Security stamp Validation Failed: [principalSecurityStamp (null)] != [getSecurityStampAsyncResults 83270b3f-a042-4a8f-b090-f5e1a084074e] also ([userManagerSecurityStamp 83270b3f-a042-4a8f-b090-f5e1a084074e] )
So principal.FindFirstValue(Options.ClaimsIdentity.SecurityStampClaimType) appears to be null. Why I dont know. I also dont know how to fix it as there are a number of third party applications calling this identity server.
update2:
I can now verify that GenerateClaimsAsync does set the SecurityStampClaim. However the CookieValidatePrincipalContext in ValidateAsync does not contain the claim in question which is strange as the comment on the method says.
/// <param name="context">The context containing the <see cref="System.Security.Claims.ClaimsPrincipal"/>
"The SlidingExpiration is set to true to instruct the handler to re-issue a new cookie with a new expiration time any time it processes a request which is more than halfway through the expiration window." For a 30 day expiration window no new cookie would be issued for the first 15 days. The first request after 15 days would issue a refreshed cookie.
The 30min timeout likely comes from the security stamp validator, which only runs every 30 minutes (the verification is expensive). It sounds like your stamps are incorrectly generated or validated. Have you configured or customized that component at all?
Side note: remove opts.Cookie.Expiration, it's ignored.
It has taken quite some time to get to the root of this issue. I am going to try to explain it here in the event someone else runs across this issue.
First off the problem was with the securty token. The security token is stored on the user table, When the identity.application cookie is created this token is stored within the cookie. Every five minutes an application contacts the identity server and checks if the token needs to be validated. If its older than thirty minutes then the security token will be validated. (Note both the five minute and thirty minutes times are configurable this is just the defaults)
This is used for something called sign out ever where. If you change your password the security token on your row in the user table will be updated. There by making it different than the one stored in the cookie on all of your devices. This will force you to be logged out ever where.
Issue nr one
SignInManager.cs#L260 validates the security token but does not test if it is null.
So if there is something wrong with the cookie and the token is null for some reason either in the database or in my case it had been over written by another cookie then the user will be logged in for thirty minutes then be kicked out the first time it tries to validate the security token. which lead to issue request #7055. The cookie should be tested each time to assure that a security token is in it.
Issue nr 2
The following line of code signs in a user and creates the cookie storing the secure token within said cookie
var signInUserResult = await _signInManager.PasswordSignInAsync(userName, password, rememberMe, true);
After much digging and debugging i found the following line which was over writing the original cookie with a new one that did not contain the security token.
await HttpContext.SignInAsync(user.Id.ToString(), user.UserName, props);
Related
We have an online assessment platform built with MVC3. It runs in IIS on a single dedicated server running Windows Server 2012 R2 over which we have full control. Recently users have been reporting that they are "getting logged out" during assessments.This is a problem as many of our assessments have time constraints and logging back in costs users valuable seconds.
I have been unable to replicate the issue during testing, but have confirmed by consulting our logs that for the last 2 months ~15-20% of users have to log back in at some point during their assessment. For the 10 months prior to that only <2% had to log back in.
I have compared our current code base to how it was 3 months ago, and nothing that is even remotely related to logging in and authentication has been changed. To my knowledge no settings on the server have been changed.
There are many hundreds of files, and thousands of lines of code in this application, but I will try and share the relevant bits in the hope that someone can help me resolve this. If there is any information that I have missed, leave a comment and I will add it as soon as possible.
From Web.config:
<authentication mode="Forms">
<forms loginUrl="~/Login/" timeout="300" slidingExpiration="true" />
</authentication>
This is how we create the auth cookie:
Guid User_Id = /* lookup id with username after verification */
string User_Role = /* CSV specifying the roles the user has */
DateTime Expiry = DateTime.Now.AddHours(5);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, User_Id.ToString(), DateTime.Now, Expiry, false, User_Role, "/");
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
cookie.Expires = Expiry;
HttpContext.Current.Response.Cookies.Add(cookie);
We user a custom implementation of the AuthorizeAttribute to restrict access to most actions like this:
public class MyController : Controller
{
[CustomAuthorize(Roles = "MyRole")]
public ActionResult MyAction()
{
// do some stuff
return View();
}
}
Which defined as follows:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
string cookieName = FormsAuthentication.FormsCookieName;
if (!filterContext.HttpContext.User.Identity.IsAuthenticated ||
filterContext.HttpContext.Request.Cookies == null ||
filterContext.HttpContext.Request.Cookies[cookieName] == null
)
{
HandleUnauthorizedRequest(filterContext);
return;
}
var authCookie = filterContext.HttpContext.Request.Cookies[cookieName];
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] roles = authTicket.UserData.Split(',');
var userIdentity = new GenericIdentity(authTicket.Name);
var userPrincipal = new GenericPrincipal(userIdentity, roles);
filterContext.HttpContext.User = userPrincipal;
base.OnAuthorization(filterContext);
}
}
This has been the setup for the last 3 years, and it has only been the last 2 months that have been problematic. As you can see the cookie is set to expire 5 hours after its creation. Users have been typically pretty vague with their reports, and looking at our logs the amount of time between initial login and having to login again ranges from anywhere between a few minutes and a couple of hours. Even so I have had a look on the server at what I think are the relevant settings and can't see any thing that may cause a problem:
The only thing I can think of that would cause them to be "logged out" would be for the authentication cookie to expire prematurely (or be deleted I guess). We are talking about thousands of users, from hundreds of companies. Many are using their own PCs rather than those provided by work. If any one has any ideas at all I would love to hear them.
Since you use Response.Add rather than SetCookie plus you define a fixed expiry date which makes cookies persistent, chances are your users have sometimes forms cookies issued twice, if they log in within these five hours twice.
I would check if this doesn't confuse the forms auth module. One of such multiple cookies could expire at some point of time and sliding expiration forces it to be reissued. This possibly opens up a scenario where there are multiple cookies in a single request, some of them could be expired.
My advice is to use an http debugger to check this and then replace the code thst possibly makes duplicates of your auth cookies.
I have implemented session sliding using in my customehttphandler module.
I am trying to acheive session sliding as well as getting authenticated on multiple website which share same ADFS server.
public void SessionAuthenticationModuleSessionSecurityTokenReceived(object sender, SessionSecurityTokenReceivedEventArgs e)
{
SessionSecurityToken token = e.SessionToken;
DateTime nowUtc = DateTime.UtcNow;
DateTime validFrom = token.ValidFrom;
DateTime validTo = token.ValidTo;
double totalMinutes = (validTo - validFrom).TotalMinutes;
double halfSpan = totalMinutes / 2;
SessionAuthenticationModule sam = sender as SessionAuthenticationModule;
if (validTo < nowUtc)
{
if (sam != null)
{
sam.DeleteSessionTokenCookie();
e.Cancel = true;
}
}
else if ((nowUtc - validFrom).TotalMinutes >= halfSpan)
{
SessionSecurityToken renewToken = sam.CreateSessionSecurityToken(
token.ClaimsPrincipal,
token.Context,
nowUtc,
nowUtc.AddMinutes(totalMinutes),
true);
e.SessionToken = renewToken;
e.ReissueCookie = true;
//db timestamp update
}
}
And SignedIn event
public void WSFederationAuthenticationModuleSignedIn(object sender, EventArgs e)
{
token = gettoken from cookie
if (token.ValidTo > DateTime.Now.ToUniversalTime())
{
//db insert for new login (assuming this will fire only once on actual login)
reissue token
}
}
Session timeout is mentioned in the my relying party application web config
<securityTokenHandlers>
<add type="Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<sessionTokenRequirement lifetime="0:02" />
</add>
</securityTokenHandlers>
Token Life time on ADFS I do not want to change which is greater than 2 minutes.
But issue is, after 2 minutes time out is not happening. It goes to SingedIn event becuase i assume it reissue token and then it calls session token received event so this condition (if (validTo < nowUtc)) never satisfy, how can i achieve timeout here? Freshness="0"achieves it but If i set Freshness="0" then I can not get authenticated by other website which are on same ADFS server. I want to be authenticated on other website as well if i have logged in one.
If I remove freshness="0" I can be authenticated without login on second website which is different application.
Why SignedIn is getting called before session token received and How can i achieve timeout in proper way and get authenticated in multiple website?
Note: I have these events in my customeHttpHanlder module. which has other event as well like PostAuthenticateRequest.
when you receive a session token, the token you receive from adfs starts expiring. After it has been completely expired it needs to be refreshed.
This is a balance between having acurate information from adfs (calling into ad each time you want to know something about the user) and having a workable situation (a signed token has a certain validity in which we trust the information to remain valid).
After the token expires, you need to get back to adfs (hence the signin event) to get a new token from adfs. The idea is that some of the information might have changed between the issuing of these two tokens.
You can implement sliding sessions on the client side (your relying parties) but that makes little sense (I'll come back to this) since you are telling yourself that the token is valid for another period. You trust yourself but the information inside the token can get out of sync and that is why you always need to go back to adfs.
All of this could make sense if you implement an automatic refresh of the token yourself. This would mean that you exchange your current token for a new one with a new validity period. I guess adfs can do this (but you need the active scenario for this). It's not a lot of code but it can be hell to setup right and I don't have any example for this.
In the end you need to ask yourself if it's worth the hassle. WIF will do an automatic signin again and a user inside the domain will be automatically logged in. A user outside the domain might have to type here credentials again. I don't think this is the end of the world.
Finally, I see you use Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler which is the old implementation. .Net 4.5 has a newer implementation..
We have a ASP.NET 4.5 WebForms application using the native forms authentication and session functionality. Both have a timeout of 20 minutes with sliding expiration.
Imagine the following scenario. A user has worked in our application for a while and then proceeds to do some other things, leaving our application idle for 20 minutes. The user then returns to our application to write a report. However, when the user tries to save, he/she is treated with the login screen, and the report is lost.
Obviously, this is unwanted. Instead of this scenario, we want the browser to be redirected to the login page the moment either authentication or session has expired. To realize this, we have build a Web Api service that can be called to check whether this is the case.
public class SessionIsActiveController : ApiController
{
/// <summary>
/// Gets a value defining whether the session that belongs with the current HTTP request is still active or not.
/// </summary>
/// <returns>True if the session, that belongs with the current HTTP request, is still active; false, otherwise./returns>
public bool GetSessionIsActive()
{
CookieHeaderValue cookies = Request.Headers.GetCookies().FirstOrDefault();
if (cookies != null && cookies["authTicket"] != null && !string.IsNullOrEmpty(cookies["authTicket"].Value) && cookies["sessionId"] != null && !string.IsNullOrEmpty(cookies["sessionId"].Value))
{
var authenticationTicket = FormsAuthentication.Decrypt(cookies["authTicket"].Value);
if (authenticationTicket.Expired) return false;
using (var asdc = new ASPStateDataContext()) // LINQ2SQL connection to the database where our session objects are stored
{
var expirationDate = SessionManager.FetchSessionExpirationDate(cookies["sessionId"].Value + ApplicationIdInHex, asdc);
if (expirationDate == null || DateTime.Now.ToUniversalTime() > expirationDate.Value) return false;
}
return true;
}
return false;
}
}
This Web Api service is called every 10 seconds by the client to check if either authentication or session has expired. If so, the script redirects the browser to the login page. This works like a charm.
However, calling this service triggers the sliding expiration of both authentication and session. Thus, essentially, creating never ending authentication and session. I have set a breakpoint at the start of the service to check if it is one of our own functions that triggers this. But this is not the case, it seems to occur somewhere deeper in ASP.NET, before the execution of the service.
Is there a way to disable the triggering of ASP.NET's authentication and session sliding expirations for a specific request?
If not, what is best practice to tackle a scenario like this?
This seems to be impossible. Once sliding expiration is enabled, it is always triggered. If there is a way to access the session without extending it, we have not been able to find it.
So how to tackle this scenario? We came up with the following alternative solution to the one originally proposed in the question. This one is actually more efficient because it doesn't use a web service to phone home every x seconds.
So we want to have a way to know when either ASP.NET's forms authentication or session has expired, so we can pro-actively logout the user. A simple javascript timer on every page (as proposed by Khalid Abuhakmeh) would not suffice because the user could be working with the application in multiple browser windows/tabs at the same time.
The first decision we made to make this problem simpler is to make the expiration time of the session a few minutes longer than the expiration time of the forms authentication. This way, the session will never expire before the forms authentication. If there is a lingering old session the next time the user tries to log in, we abandon it to force a fresh new one.
All right, so now we only have to take the forms authentication expiration into account.
Next, we decided to disable the forms authentication's automatic sliding expiration (as set in the web.config) and create our own version of it.
public static void RenewAuthenticationTicket(HttpContext currentContext)
{
var authenticationTicketCookie = currentContext.Request.Cookies["AuthTicketNameHere"];
var oldAuthTicket = FormsAuthentication.Decrypt(authenticationTicketCookie.Value);
var newAuthTicket = oldAuthTicket;
newAuthTicket = FormsAuthentication.RenewTicketIfOld(oldAuthTicket); //This triggers the regular sliding expiration functionality.
if (newAuthTicket != oldAuthTicket)
{
//Add the renewed authentication ticket cookie to the response.
authenticationTicketCookie.Value = FormsAuthentication.Encrypt(newAuthTicket);
authenticationTicketCookie.Domain = FormsAuthentication.CookieDomain;
authenticationTicketCookie.Path = FormsAuthentication.FormsCookiePath;
authenticationTicketCookie.HttpOnly = true;
authenticationTicketCookie.Secure = FormsAuthentication.RequireSSL;
currentContext.Response.Cookies.Add(authenticationTicketCookie);
//Here we have the opportunity to do some extra stuff.
SetAuthenticationExpirationTicket(currentContext);
}
}
We call this method from the OnPreRenderComplete event in our application's BasePage class, from which every other page inherits. It does exactly the same thing as the normal sliding expiration functionality, but we get the opportunity to do some extra stuff; like call our SetAuthenticationExpirationTicket method.
public static void SetAuthenticationExpirationTicket(HttpContext currentContext)
{
//Take the current time, in UTC, and add the forms authentication timeout (plus one second for some elbow room ;-)
var expirationDateTimeInUtc = DateTime.UtcNow.AddMinutes(FormsAuthentication.Timeout.TotalMinutes).AddSeconds(1);
var authenticationExpirationTicketCookie = new HttpCookie("AuthenticationExpirationTicket");
//The value of the cookie will be the expiration date formatted as milliseconds since 01.01.1970.
authenticationExpirationTicketCookie.Value = expirationDateTimeInUtc.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds.ToString("F0");
authenticationExpirationTicketCookie.HttpOnly = false; //This is important, otherwise we cannot retrieve this cookie in javascript.
authenticationExpirationTicketCookie.Secure = FormsAuthentication.RequireSSL;
currentContext.Response.Cookies.Add(authenticationExpirationTicketCookie);
}
Now we have an extra cookie at our disposal that always represents the correct forms authentication expiration time, even if the user works in different browser windows/tabs. After all, cookies have a browser wide scope. Now the only thing left is a javascript function to verify the cookie's value.
function CheckAuthenticationExpiration() {
var c = $.cookie("AuthenticationExpirationTicket");
if (c != null && c != "" && !isNaN(c)) {
var now = new Date();
var ms = parseInt(c, 10);
var expiration = new Date().setTime(ms);
if (now > expiration) location.reload(true);
}
}
(Note that we use jQuery Cookie Plugin to retrieve the cookie.)
Put this function in an interval, and users will be logged out the moment his or her forms authentication has expired. VoilĂ :-) An extra perk of this implementation is that you now have control over when the forms authentication's expiration gets extended. If you want a bunch of web services that don't extend the expiration, just don't call the RenewAuthenticationTicket method for them.
Please drop a comment if you have anything to add!
Your website functionality should work without JavaScript or you just replace one problem with another. I have tackled this problem also and here is how it was solved:
When you authenticate yourself then session cookie is created with default lifetime on 20 min. When this expires user will be logged out.
When user selects "remember me" in the sign in form then additional persistence cookie [AuthCookie] is created in client side and in the database. This cookie has a lifetime of 1 month. Whenever page is loaded, session and persistence cookie data is recreated with a new lifetime (normally you want to decrypt/crypt the ticket).
Imagine the following scenario. A user has worked in our application
for a while and then proceeds to do some other things, leaving our
application idle for 20 minutes. The user then returns to our
application to write a report. When the user tries to save, his session is restored before the request.
One way to do this is to extend global.aspx to handle prerequest. Something in the lines of:
void application_PreRequestHandlerExecute(object sender, EventArgs e){
...
if (HttpContext.Current.Handler is IRequiresSessionState) {
if (!context.User.Identity.IsAuthenticated)
AuthService.DefaultProvider.AuthenticateUserFromExternalSource();
AuthenticateUserFromExternalSource should check if cookie data matches with the database one, because anything stored in client side can be changed. If you have paid services with access rights then you need to check if user still has those rights and then you can recreate the session.
This can all be solved client side, without the need to go back to the server.
In JavaScript do this.
var timeout = setTimeout(function () {
window.location = "/login";
}, twentyMinutesInMilliseconds + 1);
The timeout will be set to 20 minutes on every page refresh. This ensures that the user needs to get all their work done before the timeout happens. A lot of sites use this method, and it saves you from doing unnecessary server requests.
I have a page where a user logs in to a back-end application via a web service. The web service returns a session ID which I want to store in a cookie for 40 minutes, as after 40 minutes the back-end application automatically closes the session.
My code to write the cookie:
private void SetCookie()
{
Response.Cookies.Add(new HttpCookie("Cookie_SessionID"));
Response.Cookies["Cookie_SessionID"].Value = ni.NicheSessionID;
Response.Cookies["Cookie_SessionID"].Expires = DateTime.Now.AddMinutes(40);
//.... after a few more things
Response.Redirect(returnUrl);
}
Then on the receiving page I have this:
private HttpCookie GetCookie()
{
HttpCookie cookie = Request.Cookies["Cookie_SessionID"];
if (cookie != null && cookie.Value != null)
{
return cookie;
}
return null;
}
For some reason the cookie returned by GetCookie() always has an Expires value of 0001-01-01 00:00:00, even though when I view cookies in the browser it has the correct expiry time.
Having read this which states expired cookies are simply not sent to the server, I assume what could be happening is that the cookie is being written correctly but the browser is not sending the expiry date because it's actually unnecessary?...
My problem is that I want to capture precisely that - the cookie has 'expired' and so they have to log in again - but I need to display a message along the lines of "I know you have already logged in but you'll need to do it again" type thing.
Thanks
The browser will not send anything to the server except the cookie name and value. All of the other properties (expires, domain, path, httponly, ...) cannot be retrieved on requests after the cookie has been set.
If you want to display such a message then you will need some other mechanism of detecting that the user was logged in. You might set a presence cookie for a year or so and check to see if it exists.
The more accepted way to deal with this is to redirect the user to a login page when they try to access a protected resource and display some message along the lines of "You need to log in to view this page. If you were previously logged in, your session may have expired."
(Also note that you should be re-setting the cookie on every request, so that the user will not be logged out if they continue to use the site. It's not clear from your code whether you are doing this or not.)
The HTTP protocol does not send cookie expiration dates to the server.
I need to revoke an authentication cookie if the user no longer exists (or some other condition), after the forms authentication mechanism already have received the authentication cookie from the browser and have validated it. I.e. here is the use scenario:
The user have been authenticated, and granted non-expiring auth cookie.
In a few days, the user tries to access my web app again, and as the cookie is valid, the forms authentication mechanism will grant access.
Now I want to perform a second check (whatever condition I want), and decide if I want to let the user continue, or to revoke the authentication.
The question is - is there an official automated way for this? So far I have come with some possibilities, but I do not know which one is better. I can capture the Authenticate event in global.asax, check whatever I want, and to revoke I clear the cookie, and then one of these:
Redirect again to same url - this should work, as this time the forms authentication will fail, and it will redirect to logon page.
Throw some exception ??? which one to make the redirect happen w/o me specifying anything?
Somehow to get the logon page url from the config file (any ideas how/which config handler to use) and redirect directly?
Some FormsAuthentication class/method I have overlooked, which is designed for this?
Any other idea?
I don't think there is an automated way to achive this.
I think the best way would be to add a date to the auth cookie which will be the last time you checked whether the user exists.
So when a user logs-in you'll:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // Ticket version
name, // Username associated with ticket
DateTime.Now, // Date/time issued
DateTime.Now.AddMonths(1), // Date/time to expire
true, // "true" for a persistent user cookie
DateTime.Now.ToUniversalTime(), // last time the users was checked
FormsAuthentication.FormsCookiePath);// Path cookie valid for
// Encrypt the cookie using the machine key for secure transport
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
FormsAuthentication.FormsCookieName, // Name of auth cookie
hash); // Hashed ticket
cookie.HttpOnly = true;
// Set the cookie's expiration time to the tickets expiration time
if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;
//cookie.Secure = FormsAuthentication.RequireSSL;
Response.Cookies.Add(cookie);
Then everytime a user is authenicated you can check the additional date you passed to the Authentication ticket and in 10 minute intervals or less double check against the database whether the user exists.
The code might look something like this:
public void FormsAuthentication_OnAuthenticate(object sender,
FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(
Request.Cookies[FormsAuthentication.FormsCookieName].Value);
DateTime lastCheckedTime = DateTime.TryParse(ticket.UserData);
TimeSpan elapsed = DateTime.Now - lastCheckedTime;
if (elapsed.TotalMinutes > 10)//Get 10 from the config
{
//Check if user exists in the database.
if (CheckIfUserIsValid())
{
//Reset the last checked time
// and set the authentication cookie again
}
else
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
return;
}
}
}
catch (Exception e)
{
// Decrypt method failed.
}
}
}
}
You can even cache the users that have been deleted the last 10 minutes and check against that collection.
Hope that helps.
If you are rejecting the cookie for some other reason than an expired session, then I think you should redirect the user to a page that describes what they need to do in order to gain access. If logging on again is sufficient, then the logon page would suffice. It sounds, however, like there are conditions under which simply logging on again is not possible. In those cases, it would be better to redirect the user to a suitable error page that describes why they are unable to access the site and explains how to gain access again (if possible).