Build an ASP.NET Session Timeout Redirect Control

By Peter Bromberg

We often get forum posts here asking "How can I tell if a user's Session is timed out, and perform some action in response?" Often this involves the incorrect assumption that the Session_End event can be used for this. ASP.NET implements a rolling timeout mechanism that extinguishes the session information for a user if no request is received within the timeout period.

Session_End happens on the server automatically regardless of whether a user has requested a page, so the idea of using it to do anything other than cleanup-type operations is a mistake; it is independent of the page lifecycle and there is no active Request or Response object to access there.

It is often important for the business logic of an ASP.NET site to know for a particular request if the user’s session information is valid (e.g., a timeout has not occurred). Without this technique it is difficult to know, when a session variable is not found, whether it was never set properly or that the user simply waited too long between requests.  Logic normally dictates that if a session is expired, any saved state needs to be recycled back to its starting state, and typically the user should also be required to re-authenticate to the site in order to enable them to start whatever process they abandoned again from the beginning.

ASP.NET developers habitually reference Session variables without ever checking for null first to see if they are actually present,  which causes the "Object reference not set" exception. That yellow error page doesn't look very professional to the user, either.
So, I set out to do some more research and see what solutions might be possible. One of the ideas I got was that a site-wide session-expiry mechanism might be overkill, since usually only certain pages (such as those involved in a shopping cart) are involved in the need for protection against expired sessions. That's what brought me to think of the idea of a "drop on the page" Session Timeout "Detect and Redirect" Control. You should be able to just drop it on the pages that need it, set the redirect url, and you would be "good to go". If you do not need a page-specific solution, you can just use the Base Page class approach, or if you don't want to use a base Page class, instead you could write an HttpModule to do this and register it in web.config.)

The only credible information I found came from a source whose work I have relied on before, Robert Boedigheimer. In sum, what Robert found was that the ASP.NET HttpSessionState class's IsNewSession( ) method returns true if a new session was created for a given request.  If this is a new session but the ASP.NET_SessionId cookie is present, this indicates a timeout situation. You may need to think about this for a while, but eventually the logic should make sense. In addition, he determined that one must access this cookie from the Request Headers collection rather than the expected Cookie collection. This is because the intrinsic Response.Cookies and Request.Cookies objects actually share the same collection, and in this test we only want to inspect the actual cookie from the Request Headers.

With this information in hand, it was very easy to create a "non-visible" ASP.NET Server control that would hook and override a late Page LifeCycle event, PreRender, to perform this check. Add a public property for the RedirectUrl, call SignOut on any Forms Authentication to force the user to login to the site over again, and send them to the login page. Let's take a look at the code for the control:

namespace PAB.WebControls
{
    using System;
    using System.ComponentModel;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
 
    [DefaultProperty("Text"),
        ToolboxData("<{0}:SessionTimeoutControl runat=server></{0}:SessionTimeoutControl>")]
    public class SessionTimeoutControl : Control
    {
         private string _redirectUrl;
 
        [Bindable(true),
            Category("Appearance"),
            DefaultValue("")]
        public string RedirectUrl
        {
            get { return _redirectUrl; }
 
            set { _redirectUrl = value; }
        }
 
 
        public override bool Visible
        {
            get { return true; }
 
 
        }
 
 
        public override bool EnableViewState
        {
            get { return false; }
        }
 
 
        protected override void Render(HtmlTextWriter writer)
        {
            if (HttpContext.Current == null)
                 writer.Write("[ *** SessionTimeout: " + this.ID + " *** ]");
            base.Render(writer);
        }
 
 
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
             if (this._redirectUrl == null)
                throw new InvalidOperationException("RedirectUrl Property Not Set.");
            if (Context.Session != null)
            {
                 if (Context.Session.IsNewSession)
                {
                    string sCookieHeader = Page.Request.Headers["Cookie"];
                     if ((null != sCookieHeader) && (sCookieHeader.IndexOf("ASP.NET_SessionId") >= 0))
                     {
                          if (Page.Request.IsAuthenticated)
                          {
                              FormsAuthentication.SignOut();
                         }
                          Page.Response.Redirect(this._redirectUrl);
                    }
                }
            }
        }
    }
}

n the OnPreRender event, we let the page control tree do it's thing first, then we do ours. First I check to see if the developer forgot to set the RedirectUrl property on the Control, because without that we would be all dressed up with no place to go. Then we check to see if there is actually a Session, and if so, we check the IsNewSession property. If it is true, we need to check our cookie, so we strip it out of the Request's Headers Collection and test for the "ASP.NET_SessionId" standard cookie name. If the sCookieHeader string is not null and the "ASP.NET_SessionId" cookie name is there, we know we have a timeout situation for this user. First we check to see if Authentication is being used and call the Forms SignOut method. This will ensure that the user must re-authenticate. Finally, we redirect the user to the specified redirect page on the site.

The beauty of this arrangement is that it requires no base page class to inherit from; nor does it have to be called for every Request. You simply drop the control onto any page that needs to be involved in this process. Not only that, but because there is a separate instance of the control on each page that needs it, you could even have more than one RedirectUrl depending on the particular business logic, each presenting different information to the user. The downloadable solution below contains the source code and project for the Control, as well as a test web project with a starting page (that has the control) and a redirect page. The Session Timeout in the web.config is set to 1 minute so you can easily test it.   Just wait at least one minute after requesting the TestPage.aspx and then refresh it. You'll be redirected since the Session has timed out.

NOTE: Based on a recent user post, I think it is important to understand that we are talking about Session State here, not the Timeout property of a Membership FormsAuthentication ticket. They are two completely different and separate things.  Alsom a reader came up with a potential situation where somebody might return to the site after visiting another site and still have their session cookie, thereby possibly triggering the redirect. One way to cover this would be to add the following line of code in the control just before the redirect line:

    Page.Request.Cookies["ASP.NET_SessionId"].Expires = DateTime.Now.AddDays(-100);

Download the Visual Studio 2010 Solution demo.


Popularity  (12803 Views)
Picture
Biography - Peter Bromberg
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites. Follow Microsoft MVP
Create New Account
Article Discussion: Build an ASP.NET Session Timeout Redirect Control
Peter Bromberg posted at Thursday, March 31, 2011 3:52 PM
Ove replied to Peter Bromberg at Sunday, April 17, 2011 2:29 PM
Hello.

I was reading this article with great interest. Having one question though:
After from what I read when using a masterpage the life cycle is a bit different, and the "guest page" fires first.

My biggest problems with session timeout comes from using membership.getuser i.e.

Could these problems along with general session timeout detection be solved putting the control on the masterpage?

Hope you have time to answer this.


Best regards

Ove Schei
Peter Bromberg replied to Ove at Sunday, April 17, 2011 2:29 PM
Membership.GetUser by itself has nothing to do with a Session. Perhaps you are thinking of the FormsAuthentication Timeout? That's a completely different thing.
Ove replied to Peter Bromberg at Sunday, April 17, 2011 2:29 PM
Well both actually. This control solves my problem with session variables, but still the question though: will it work putting the control on a masterpage, or will it have to be on every child page?

Then  you mention FormsAuthentication Timeout, and I feel stupid not knowing that one :(
That's the trouble that lead me to your page in the first place. Solved the session issue but raised a new one.
I'll do some research on that since I need to sort it out as well.

But the question about master/child page I find interesting due to page-life-cycle, of maybe it doesn't matter due to the way you solved it.
Bryan Johns replied to Peter Bromberg at Sunday, April 17, 2011 2:29 PM
I put the control on a simple test page that stores a string in a session state variable.  When I run the page in debug mode it redirects the first time the page is hit.  Should the redirect code be wrapped in an if statement so that it only happens on postback?
Peter Bromberg replied to Ove at Sunday, April 17, 2011 2:29 PM
The Forms Authentication timeout property controls the Forms Authentication "ticket" (a cookie). When the timeout is reached, as long as your web.config entries are correct, the user would be automatically redirected to your login page.
Peter Bromberg replied to Bryan Johns at Sunday, April 17, 2011 2:29 PM
"I put the control on a simple test page that stores a string in a session state variable.  When I run the page in debug mode it redirects the first time the page is hit.  Should the redirect code be wrapped in an if statement so that it only happens on postback?"

If you tried the demo Visual Studio Solution it is preconfigured for a one-minute session timeout. The Testpage.aspx does nothing but if you were to create a session variable there it should not redirect UNTIL a request is made and the session has expired. 

So, you have something wrong in your setup. Refer to the sample solution.
Bryan Johns replied to Peter Bromberg at Sunday, April 17, 2011 2:29 PM
I copied the <sessionState ... /> setting from your web.config and pasted it into my project.  When I first hit the page, it works as expected.  However, on subsequent visits, it redirects every time.  Could it be that my browser is holding onto the cookie?  Each new visit creates a new session.  So it checks the cookie and thinks there was a session timeout.
Peter Bromberg replied to Bryan Johns at Sunday, April 17, 2011 2:29 PM
i would run the sample in debug mode and put a breakpoint on this line in the control:

 protected override void OnPreRender(EventArgs e)
{   <= breakpoint here

then you can F11 single step through the code and see what's happening
Bryan Johns replied to Peter Bromberg at Sunday, April 17, 2011 2:29 PM
Did that.

If I go to the page with a freshly restarted browser (no cookie) it works as expected.  If I browse to another site, and come back to the test page without closing the browser completely or otherwise clearing the cookie, it will redirect every time.

First it checks to see that the redirecturl is set, throwing an exception if it's not.  Then it checks to see if Context.Session is null.  If it's not, it checks to see if Context.Session.IsNewSession.  If it is, it looks for the sessionid cookie.  If it finds it, then it redirects.

I think this behavior will be OK in most cases.  It would only be a problem if the control is placed on the landing page for the site.  Here's an example scenario.

In a site at www.somedomain.com, the control is on default.aspx and set to redirect to redirect.htm when session expires.  A user starts their browser and goes to www.somedomain.com and default.aspx loads, starting a new session.  The user proceeds to do stuff on the site and in the process a session variable is stored and the sessionid cookie is set.  The user leaves the site and goes elsewhere on the web.  Later, possibly hours or even days, they come back to www.somedomain.com, without having closed their browser, and default.aspx loads starting a new session.  Since they didn't close their browser, the old sessionid cookie is still present.  The control detects it and redirects them even though this is a new visit to the site.

The only way I can see to prevent the redirect in this scenario is to not put the control on pages that can be reached directly such as an unsecured default.aspx.
Marc replied to Bryan Johns at Sunday, April 17, 2011 2:29 PM
The issue you raise is solved by forcing the log out. So your scenario should never happen.

Page.Response.Redirect(this._redirectUrl);
Marc replied to Marc at Sunday, April 17, 2011 2:29 PM
sorry wrong line of code, meant this one....

FormsAuthentication.SignOut();
Feras replied to Marc at Sunday, April 17, 2011 2:29 PM

Probably my question is stupid but it is driving me crazy, you see I have this application its session is not expiring after logging out even though I have used Session.Abandon(), Session.Clear(), and Session.Removeall(). I have been searching all over the internet but no luck so far and I really wish I can get some help. Say I have user X if I do the following any one can login with X's account:

  1. Login with X's username and password.
  2. Take Session ".ASPXFORMSAUTH" info.
  3. Logout from X's account
  4. Add the Session ".ASPXFORMSAUTH" with its value.
  5. type the URL and click enter

the page just opens up and it is really driving me CRAZY!!, kindly will you tell me how to fix this issue?

Thanks in advance

Mark Kucera replied to Feras at Sunday, April 17, 2011 2:29 PM
So is there any specific reason to override OnPreRender vs. overriding OnInit?  I'm trying OnInit() and it seems like it's having the desired effect because this fires before Page_Load() (where problematic code that might try to call a session variable is stored).  I made a couple of other small mods to this code that seem to make it pretty flexible.  In the main method that checks for the new session i removed the check that ensures _redirectUrl is set.  instead i substituted this code  at the end of the method:

if (string.IsNullOrEmpty(_redirectUrl))
{
    FormsAuthentication.RedirectToLoginPage();
    Page.Response.End();
}
else
{
    Page.Response.Redirect(this._redirectUrl, true);
}

This allows the developer the ability to specify a redirect URL, but doesn't make it required.  RedirectToLoginPage makes a Response.Redirect() call but doesn't include the Response.End() parameter so that is why i explicitly added Page.Response.End().

I haven't released this to production yet but so far it seems like it's doing what i want it to do.

Comments appreciated!
-MK