Tuesday, July 14, 2009

ASP.NET Session Expiration Redirect

Most of the applications I work with require some form of authentication and a timed expiration of the authentication ticket and session object. When the expiration takes place it can wreak havoc on your application code if you are relying on the Session object or have authentication code in place to ensure that the user is authenticated before you serve up any data for them.

Unfortunately ASP.NET doesn't provide a nice out of the box solution for handling the session timeout gracefully. Everything expires behind the scenes and your user is left unaware of what has happened. Additional problems can arise if the user abandoned their browser in a state that you didn't code for, which can result in errors or exceptions taking place.

With this in mind I like to put a Session Expired page in place in my applications and pro-actively send the user there when their session ends. This accomplishes a few different things:

1) Provides a nice user experience for your users
2) Prevents your application from being left in an unknown state when the session / auth ticket expires
3) Prevents application errors and exceptions from occurring when a user tries to perform an action on a page after their session / auth ticket has expired

Nested MasterPage(s) For All Authenticated Pages

You need to implement a MasterPage (or a Page base class) for all of your authenticated pages so you can inject a bit of javascript to handle the redirect on session timeout.

Get Your Web.config Settings Straight

Make sure that your auth ticket timeout and session timeout match.

<system.web> 
    <!-- 
        Session configuration 
        - Timeout value needs to match Forms Auth timeout** 
    --> 
    <sessionState timeout="45" /> 
    <!-- 
        The <authentication> section enables configuration 
        of the security authentication mode used by 
        ASP.NET to identify an incoming user. 
    --> 
    <authentication mode="Forms"> 
        <forms loginUrl="Login.aspx" 
           protection="All" 
           path="/" 
           requireSSL="false" 
           timeout="45" 
           name=".ASPXAUTH" 
           slidingExpiration="true" 
           defaultUrl="Login.aspx" 
           cookieless="UseDeviceProfile" 
           enableCrossAppRedirects="false" /> 
</system.web> 


Create Your Session Expiration Page



Create a page for your users to be taken to when their session expires. I created SessionExpired.aspx with the following message:



<h2>Session Expired</h2>                   
<p> 
    <span style="color:White;">Your session has expired due to inactivity.</span> 
    <br /> 
    <br /> 
    <a href="Login.aspx">Click here to login again</a> 
</p> 


Ensure the Authentication ticket is signed out in your code behind:



FormsAuthentication.SignOut();


Add The Session Expiration Javascript To Your Page_Load



In the Page_Load method of your MasterPage (or base page class) add the following code:



// Handle the session timeout 
string sessionExpiredUrl = Request.Url.GetLeftPart(UriPartial.Authority) + "/SessionExpired.aspx"; 
StringBuilder script = new StringBuilder(); 
script.Append("function expireSession(){ \n"); 
script.Append(string.Format(" window.location = '{0}';\n", sessionExpiredUrl)); 
script.Append("} \n"); 
script.Append(string.Format("setTimeout('expireSession()', {0}); \n", this.Session.Timeout * 60000)); // Convert minutes to milliseconds 
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "expirescript", script.ToString(), true);


That's It



The session timeout script should be injected into each of your authenticated pages now. The client's browser will begin the countdown after each page has loaded or each PostBack has occurred. Once the countdown is reached the browser will redirect to your SessionExpired page.



Reference:

http://msmvps.com/blogs/shahed/archive/2007/09/05/redirect-to-login-page-on-session-expiration-asp-net.aspx



Aaron Schnieder

http://www.churchofficeonline.com

9 comments:

Raj Kaimal said...

You don't need javascript to redirect to the timeout page. You can use the "refresh" header tag like so:

Response.AppendHeader("Refresh", "60; URL=SessionExpired.aspx")

Raj

Rodrigo said...

And how about the applications using Asp Ajax??.

I have problems detecting when the user session is expired, for example
when the user load the page and the session out after some minutes.

When the user perform a clik after the session did expired, a exception is thrown:

Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled.

Details: Error parsing near '


...

Mark Hildreth said...

Just a head's up. This isn't always as straight forward as it seems. We battled with this for a long time and could figure out the issue. It turns out that the authentication ticket is not reissued on every request. It is only reissued after you've hit the internal threshold for the age of the ticket. That age is 1/2 the legnth of the timeout. Therefore, if you have a timeout of 40 minutes, and you make a request at 19 minutes, the ticket is not refreshed. If you make a request at 21 minutes, you will then get a new ticket expiring 40 minutes from minute 21.

The same holds true during async-postbacks, so you may want to register your script with the ScriptManager instead of Page.ClientScript.

Kiyoshi said...

For me, I use Global.asax as all pages will use this, including the Masterpage.
In the Global_AcquireRequestState event I use logic to determine if there is session data and if not redirect. Use Session_Start and Session_End to manage session variables. Ajax requests could also funnel through the Global.asax if they are using sessions.

Global.asax is also a great place to trap unhandled exceptions and log/redirect.

Aaron Schnieder said...

Kiyoshi:

Global.asax is definitely a good handler for exception logging, etc. However, in the case of detecting an expired session you are relying on a server side request to take place to check whether or not the user's session has timed out. If the user tries to invoke some client side functionality global.asax will not be triggered and the session end will not be handled correctly, which can lead to errors.

Aaron Schnieder said...

Mark:

Thanks for the info, I will have to look into that.

Aaron Schnieder said...

Raj:

Your solution would work, but it does not rely on the Session.Timeout variable and therefore could easily get out of sync with your web.config session settings.

Anonymous said...
This comment has been removed by a blog administrator.
Phuff34 said...

Kiyoshi:

Something else to keep in mind - Session_End NEVER gets called if your web application is in a web farm scenario and you have SQL server for your state management.