Expiring Session via AJAX using HTTP Response Headers

Posted by Dan on Oct 9, 2007 @ 9:46 AM

Raymond Camden recently asked on his blog How can you timeout a session in an Ajax-based application?. Most of the comments on the entry relate to doing some kind of server-ping, but my suggestion would be to just let your application tell your Ajax code when the session actually expires.

The concept is to use HTTP response headers to inform your Ajax request when the session has actually expired. This allows you to do everything in one single call, without having to worry writing other code. Besides you should be adding some failure code to your Ajax calls anyway, so this fits right in with good Ajax practices.

There are two basic approaches you can take. Using a "custom" response header or sending back HTTP status code to indicate the user is no longer authorized to view content.

I've put together an example page that shows off how you can implement either of the techniques.

In my HTTP Status Code example I use a 403 - Forbidden to indicate when the user's session has expired—which fits in nicely with the existing HTTP specification (using a 403 will not prompt the browser to reauthorize the user. You could use a 401 HTTP status code if you wanted to force your use to log back in.)

In my Customer HTTP Response Header example, I just pass back to the browser a customer response header of "sessionState" and then use the XHR's native getResponseHeader() method to retrieve the value of the header. You can then take appropriate action based on the header's value.

I've tried to keep the examples very basic and I stuck to just the guts of the technique. Obviously, you'd still need to implement code around this to actually notify the user that their session is expired or to get them to re-login.

Ajax Session Expired Examples

Categories: JavaScript, HTML/ColdFusion, jQuery, Source Code

10 Comments

  • Dan, started reading this on Ray's blog and followed the links here. So, are you saying that you'd have something for instance in your onRequestStart method like:
    <cfif (not structKeyExists(session, 'loggedin') or (structKeyExists(session, 'loggedin') and not session.loggedin)) >
      <cfheader statuscode="403" statustext="Forbidden" />
    </cfif>

    Then you'd just have all your ajax calls check for this status and handle it appropriately (i.e. window.location = 'login.cfm';)?

    Does that sound about right?
  • @Chris:

    How you implement the solution is going to really be up to your framework. If you put it in your onRequestStart up and are not filtering that code for AJAX specific calls, then you'll be executing that logic every time a session has expired (which if you're using the header status isn't necessarily a bad thing.)

    As for how to handle it appropriately, I generally wouldn't recommend relocating the user on an AJAX operation. I'd instead just give them a message that their session has expired. At least that's what I'd recommend for AJAX operation that are a result of direct user interaction.
  • @Dan -

    I'm experimenting with this technique, and I'm just trying to get my JavaScript (jQuery) to recognize what the statusCode was set to. In my onRequestStart I set the status code to 202 "Accepted", but I'm unclear about how to get at this value with the XMLHTTPRequest object.

    When I dump all of the headers ( getAllResponseHeaders() ), I get the following:
    Date    Wed, 23 Sep 2009 02:38:01 GMT
    Server    Apache/2.2.9 (Win32) JRun/4.0 PHP/5.2.6
    Keep-Alive    timeout=5, max=100
    Connection    Keep-Alive
    Transfer-Encoding    chunked
    Content-Type    text/html; charset=UTF-8

    I don't see my 202 in there anywhere. I notice in firebug though that the status of the request is 202... so the <cfheader> tag is doing its job, but I'm not able to get access to that for some reason. I'm going to keep looking for a solution, but if you could expand further on how you'd use the native getResponseHeader() (i.e. specifically what header do you request of the method), that'd be great. :)

    Thanks
  • @Dan,

    Okay, I figured this out (after some sleep and much googling) what we actually want to access isn't in the getResponseHeader() method, but rather the status and statusText *properties* of the XMLHTTPRequest object. So, accessing the numeric portion like XMLHTTPRequest.status and the textual portion like XMLHTTPRequest.statusText... That may have been understood, just not by me. :)

    Anyway, now to figure out the rest! :)
  • @Chris:

    If you actually view the source at http://www.pengoworks.com/workshop/jquery/session_... you should see that it has jQuery code in there checking the XHR object's status code.

    Also, instead of setting a positive header (202 - Accepted) I'd instead only change the header if there's an issue--like the session is expired. The reason is you don't want to change the status if there's an error (which normally returns a 500 status code.) I'd also probably leave the default 200 status code alone (but that would be my preference.)

    Controlling the headers can be very useful. For example, your normal AJAX response might be a JSON packet--but what happens if an error occurs?

    Using the returned HTTP status code, you could check for non-200 status codes and if that exists, just output the results to the screen for the user. CF natively returns a 500 status code for errors, but if you're manually handling your errors you can still force this header back to the AJAX call so know that the request didn't process normally.

    Lots of really cool stuff can be done with the headers.
  • Hey Dan,

    I'll look at the source on your example page for the jQuery code you're using. Don't know why that didn't occur to me.

    As for changing the success code, of course I would normally leave that alone and let the default 200 come through. I was just experimenting and my session wasn't timing out for some reason. I wanted to see *something* happen, so I changed the header from 200 to 202 on success and to 403 if the session.loggedin variable was either not defined or was set to false.

    Thanks for a great article and for responding so quickly to my questions! :)
  • @Chris:

    Glad to help!
  • Example for php/jquery

    Server (php code):

    /**
    */
    function is_xhr() {
        if(isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
            stristr($_SERVER['HTTP_X_REQUESTED_WITH'], 'XMLHttpRequest') !== FALSE) {
                return true;
        } else {
            return false;
        }
    }

    if (!check_user_login()) {

            if(is_xhr()) {
                header('HTTP/1.0 403 Forbidden');
                exit;
            }

            user_goto('/index.php');
        }
    #####

    Here, I check the session expiration state with the check_user_login() function. After, if the request containt the HTTP_X_REQUESTED_WITH' linked to Ajax request, I return the 403 status code.

    #####

    Client (javascript):

        $(document).ready(function() {

            // Configure the request for password generation
            $.ajaxSetup({
                type: 'POST',
                url: 'reseller_edit.php',
                data: 'edit_id={EDIT_ID}&uaction=genpass',
                datatype: 'text',
                beforeSend: function(xhr){xhr.setRequestHeader('Accept', 'text/plain');},
                success: update_fields,
                error: ispCPajxError
            });
        });

    function ispCPajxError(xhr, settings, exception) {

        switch (xhr.status) {
            // We receive this status when the session is expired
            case 403:
                window.location = '/index.php'
            break;
            default:
                alert('HTTP ERROR: An Unexpected HTTP Error occurred during the request');
            break;
        }
    }

    Here, if an error occur, I check the status code. If the status code is 403, I made redirection to the login page.

    Sorry for my poor english, I'm french ;
  • Example for php/jquery

    Server (php code):

    /**
    */
    function is_xhr() {
        if(isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
            stristr($_SERVER['HTTP_X_REQUESTED_WITH'], 'XMLHttpRequest') !== FALSE) {
                return true;
        } else {
            return false;
        }
    }

    if (!check_user_login()) {

            if(is_xhr()) {
                header('HTTP/1.0 403 Forbidden');
                exit;
            }

            user_goto('/index.php');
        }
    #####

    Here, I check the session expiration state with the check_user_login() function. After, if the request containt the HTTP_X_REQUESTED_WITH' linked to Ajax request, I return the 403 status code.

    #####

    Client (javascript):

        $(document).ready(function() {

            // Configure the request for password generation
            $.ajaxSetup({
                type: 'POST',
                url: 'reseller_edit.php',
                data: 'edit_id={EDIT_ID}&uaction=genpass',
                datatype: 'text',
                beforeSend: function(xhr){xhr.setRequestHeader('Accept', 'text/plain');},
                success: update_fields,
                error: ispCPajxError
            });
        });

    function ispCPajxError(xhr, settings, exception) {

        switch (xhr.status) {
            // We receive this status when the session is expired
            case 403:
                window.location = '/index.php'
            break;
            default:
                alert('HTTP ERROR: An Unexpected HTTP Error occurred during the request');
            break;
        }
    }

    Here, if an error occur, I check the status code. If the status code is 403, I made redirection to the login page.

    Sorry for my poor english, I'm french ;
  • On dot net framework 3.5,

    use ScriptManager and set EnablePageMethods = "true".

    Source of the page:

      <script type="text/javascript">
        function CheckSession() {
          PageMethods.CheckSession(OnSucceeded, OnFailed);
          return false;
        }

        function OnSucceeded(res) {
          if(res == '0')
          {
            alert('Your session is stil active please proceed.');
          }
          else
          {
            alert('Your session has timed out. Please log in again.');
          }
        }

        function OnFailed(res) {
          alert(res);
        }
      </script>
    </head>
    <body>
      <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager" runat="server" EnablePageMethods="true" />

    Code behind:

        [WebMethod]
        public static string CheckSession()
        {
          if (HttpContext.Current.Session.IsNewSession)
            return "1";
          else
            return "0";
        }

        protected void login_Click(object sender, EventArgs e)
        {
          //session is established once there is at least one item in the session
          Session["test"] = "a";
        }

    Web.config:

            <httpModules>
                <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
            </httpModules>

        <!--
          Set compilation debug="true" to insert debugging
          symbols into the compiled page. Because this
          affects performance, set this value to true only
          during development.
        -->
        <compilation debug="true">
                <assemblies>
                    <add assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/></assemblies></compilation>
        <!--
          The <authentication> section enables configuration
          of the security authentication mode used by
          ASP.NET to identify an incoming user.
        -->
        <authentication mode="Windows"/>

    and

    <sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes" cookieless="false" timeout="1"/>

Comments for this entry have been disabled.