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?
Best Answer
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.
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 ourSetAuthenticationExpirationTicket
method.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.
(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!