10

If the session has expired and the user clicks on a link to another webform, the asp.net authentication automatically redirect the user to the login page.

However, there are cases when the user does not click on links to other webforms. For example: edit link in gridviews, when using AutoCompleteExtender with textboxes and the application attempts to get the information, and basically, in every case when a postback is done and the event is not automatically handled by the asp.net authentication.

What is the best way to handle these exceptions?

UPDATE: I have just modified the question title: forms authentication timeout, instead of the initial session timeout. Thanks for making me aware of this difference.

UPDATE: I have just created a new question with the specific problem I am facing: How to handle exception due to expired authentication ticket using UpdatePanel?. Surprisingly, I have not found much information about it. I would really appreciate your help.

Community
  • 1
  • 1
aleafonso
  • 2,244
  • 8
  • 38
  • 58
  • 1
    It's not that the request "is not handled by the asp.net authentication". It is. Rather, the Ajax's XmlHttpRequest returns the non-XML response to the browser. The problem is then rather at the browser's side and it is there where you should find the answer for your issue. – Wiktor Zychla Sep 28 '11 at 16:56
  • What do you want to happen when the session has timed out and the user clicks and edit link in a GridView? – James Johnson Sep 28 '11 at 18:05
  • Hi @James. If the session has expired I want to take the user to the login page. Therefore, if the edit link is clicked (or any other postback is generated), I should handle the exception generated and take the user to the login page. So far the only solution I have thought of has been to add try-catch to every methos handling postbacks but I'm sure there must be better ways to do this – aleafonso Sep 29 '11 at 07:43
  • @aleafonso just curious why you unaccepted this months later? – Adam Tuliper Jan 13 '12 at 17:11
  • @AdamTuliper look at the comments below your answer. It describes what the situation is. I'm still having the same problem. – aleafonso Jan 16 '12 at 08:18
  • @aleafonso see my response below – Adam Tuliper Jan 16 '12 at 15:26

3 Answers3

10

This is why many systems include timers on the page to give approximate timeout times. This is tough with interactive pages. You really need to hook ajax functions and look at the return status code, which is a bit difficult. One alternative is to use code based on the following which runs early in the page lifecycle and perform an ajax redirect to a login page. Otherwise you are stuck trying to intercept the return code from ajax and in asp.net where the ajax is done 'for you' (ie not a more manual method like jQuery) you lose this ease of detection.

http://www.eggheadcafe.com/tutorials/aspnet/7262426f-3c65-4c90-b49c-106470f1d22a/build-an-aspnet-session-timeout-redirect-control.aspx

for a quick hack you can try it directly in pre_init http://forums.asp.net/t/1193501.aspx

Edit what is wanted are for forms auth timeouts, not session timeouts. Forms auth timeouts operate on a different scale than session timeouts. Session timeouts update with every request. Forms auth tickets aren't actually updated until half of the time goes by. So if you have timeouts set to an hour and send in one request 25 minutes into it, the session is reset to an hour timeout, the forms auth ticket isnt touched and expires in 35 minutes! To work around this, sync up the session timeout and the forms auth ticket. This way you can still just check session timeouts. If you don't like this then still - do the below and sync up the timeouts and then parse the auth ticket and read its timeout. You can do that using FormsAuthentication.Decrypt - see:

Read form authentication cookie from asp.net code behind

Note that this code requires that upon login you set some session value - in this case its "UniqueUserId". Also change the login page path below to fit yours.


protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
        {
            //Only access session state if it is available
            if (Context.Handler is IRequiresSessionState || Context.Handler is IReadOnlySessionState)
            {
                //If we are authenticated AND we dont have a session here.. redirect to login page.
                HttpCookie authenticationCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
                if (authenticationCookie != null)
                {
                    FormsAuthenticationTicket authenticationTicket = FormsAuthentication.Decrypt(authenticationCookie.Value);
                    if (!authenticationTicket.Expired)
                    {
                        if (Session["UniqueUserId"] == null)
                        {
                            //This means for some reason the session expired before the authentication ticket. Force a login.
                            FormsAuthentication.SignOut();
                            Response.Redirect("Login.aspx", true);
                            return;
                        }
                    }
                }
            }
        }

Community
  • 1
  • 1
Adam Tuliper
  • 29,982
  • 4
  • 53
  • 71
  • Hi Adam. I have just had a look at your recommended posts. However, I think these solution could work for session timeouts. My fault, I should have made clear my question was about the forms authentication "ticket" timeout. I have just updated it accordingly. Any other idea on how to solve this issue? Thanks a lot for sharing – aleafonso Sep 29 '11 at 10:24
  • Hi again Adam. Where should I add this procedure? I first thought I should add it to the MasterPage since all the pages use the same one but when I clicked on the GridView Edit link it did not catch the event. I also tried adding the code the webform but, surprisingly, it did not catch the event either. I wonder if this has something to do with the partial call caused by the UpdatePanel. So, where can I add the code? – aleafonso Sep 30 '11 at 16:30
  • Hi Adam. I have just tried this solution. However, if the ticket has expired and I click on the Edit link in a GridView (whithin an UpdatePanel), the event is not handled by "Application_PreRequestHandlerExecute" since it breaks before it gets there (I assume). I know the error is related to the ticket expiration as it does not break when the ticket has not expired. Any other idea will be more than welcome! – aleafonso Oct 03 '11 at 08:04
  • you are saying when the ticket has expired, PreRequestHandlerExecute never executes? – Adam Tuliper Oct 19 '11 at 04:53
  • Response.Redirect does not work for ajax calls. You need to detect ajax calls and do special processing, for example return a 409 and add a X-Redirect header containing the url to redirect to. On javascript side using jQuery, set a global error handler and handle the 409 special case so it redirects to the content of the X-Redirect header. – Softlion Oct 25 '11 at 15:50
  • @Softion that is not entirely accurate, there is a much easier way to set this up in web.config if I recall without having to do the checks you mentioned. I'll update this when not on phone but it is important to bring up though thanks. – Adam Tuliper Oct 26 '11 at 06:45
  • @Softion note the web.config method here http://ranafaisal.wordpress.com/2008/06/02/responseredirect-in-aspnet-ajax/ using the ScriptModule – Adam Tuliper Oct 26 '11 at 14:34
  • Thank you for the answer @Adam. At the beginning I was confused because in Debugging mode it breaks before the error is handled by the PreRequestHandlerExecute. Now I have tried this without Debugging and it actually redirects me to the login page. Regards, – aleafonso Oct 28 '11 at 11:40
  • I get a popup from Visual Studio: Microsoft JScript runtime error: Sys.ArgumentNullException: Value cannot be null. Parameter name: array --- In the file: ScriptResource.axd...................... In this set of instructions: $type.indexOf = function Array$indexOf(array, item, start) { var e = Function._validateParams(arguments, [ {name: "array", type: Array, elementMayBeNull: true}, {name: "item", mayBeNull: true, optional: true}, {name: "start", mayBeNull: true, optional: true} ]); if (e) throw e; return indexOf(array, item, start); – aleafonso Oct 28 '11 at 15:52
  • @Adam Tuliper: the scriptModule works only with Microsoft Ajax. It does not work with ASP.NET MVC / jQuery. – Softlion Nov 02 '11 at 17:38
  • 1
    @Softion sure but this post was not tagged MVC and was for web forms. – Adam Tuliper Nov 03 '11 at 04:25
  • @AdamTuliper I am using web forms! Certainly I use jQuery (most of the time jQuery-ui). I am not too sure that this is why I am getting the error, since it may occur when I click on a link in the gridview, and jquery is not involved at all. – aleafonso Nov 03 '11 at 17:27
  • ah since you are using jquery here I believe you need to set a response code on the server side (instead of a redirect). Your jQuery ajax method must check this. Im not sure about the control of tje jquery ui call you have and if you can set a 'redirect' value on your json. if you cant, check out brians answer on there to detect the text response from the redirect I had in the code above. – Adam Tuliper Jan 16 '12 at 15:26
1

If you're using Forms Authentication, the user will be redirected to the login page when the Forms Authentication ticket expires, which is not the same as the Session expiring.

You could consider increasing the Forms Authentication timeout if appropriate. Even to the extent of using a persistent cookie. But if it does expire, there's no real alternative to redirecting to the login page - anything else would be insecure.

One way to deal with Session timeouts is to use Session as a cache - and persist anything important to a backing store such as a database. Then check before accessing anything in Session and refresh if necessary:

MyType MyObject
{
    get
    {
        MyType myObject = Session["MySessionKey"] as MyType
        if (myObject == null)
        {
            myObject = ... get data from a backing store
            Session["MySessionKey"] = myObject;  
        }
        return myObject;
    }
    set
    {
        Session["MySessionKey"] = value;
        ... and persist it to backing store if appropriate
    }
}
Joe
  • 122,218
  • 32
  • 205
  • 338
  • Hi Joe. I am indeed using Forms authentication and I've just updated the question accordingly. During the login, if the user wants to be remembered, I create a persistent cookie, otherwise, I just create the authentication "ticket". If the authentication ticket has expired and the user requests another webform it gets redirected to the login page (which is alright). My problem comes when the users have waited too long to do any event (other than page requests) involving a postback and the ticket has already expired. What can I do in this case? Thanks a lot for your time – aleafonso Sep 29 '11 at 10:17
0

If you're using a master page or a base page, I would add some logic to one of the events in the page lifecycle to check whether the session is new:

protected void Page_Load(object sender, EventArgs e)
{
    if (Session.IsNewSession)
    {
        //do whatever you need to do
    }
}
James Johnson
  • 45,496
  • 8
  • 73
  • 110
  • 1
    I believe that saying "Session timeout" he actually means "authentication session timeout" like timing out of the forms cookie. And your suggestion would be rather not helpful for detecting that the authentication is no longer valid. – Wiktor Zychla Sep 28 '11 at 18:29
  • That's exactly what I meant. Thanks for clarifying @Wiktor. Sorry for the misunderstanding James. – aleafonso Sep 29 '11 at 07:39
  • A problem with adding forms authentication logic to the master page's code is that this logic is executed after the content page's code. – JohnH Jan 26 '17 at 14:38