Ask a Jedi: How can you timeout a session in an Ajax-based application?

Todd Sharp, who is normally the one providing me CF8-Ajax based answers, asked me this question today:

Imagine you have an Ajax-based site. The front end acts like a dashboard. In other words, the user never leaves the page, but executes various actions that do Ajaxy-type things on the back end. But if the user sits by and does nothing, how can I recognize a session timeout when the next Ajax-based call is done?

So Todd and I have been talking about this off and on all morning, so pardon the randomness of the response. In general what I recommended was this:

Build a Ping service. Your application can ping the server every minute. As you know - this will keep your session alive. However - what you can do is simply use onSessionStart to define a session variable named lasthit. In your onRequestStart - update lasthit to now(). In your ping, simply pass a URL parameter that will flag onRequestStart to not update the variable.

Your ping request can handle the timeout anyway it wants. You can return the number of minutes left till timeout, and if less than 5 minutes, use a new ColdFusion-Ajax window to prompt the user. (Much better than an alert!) You can not warn them at all, but use location.href to push them to a timeout page (and do a manual structClear on the session).

On an interesting side note - Spry has native support for this. By returning a particular string instead of your normal XML/JSON packet, you can fire off a 'Session Ended' event. (See my article on this feature here.) ColdFusion's Ajax support does not - although you can kind of do it by using the onError support in ColdFusion's various Ajax-based tags. This support gives you access to the error thrown, so you could introspect the error and run your own handler for the session ending then.

So as always - I'm open to suggestions and options. Has anyone built something like this into an Ajax-application? Everything I've built so far with Ajax only uses Ajax in parts of the site - not everywhere.

Comments

Ray, that would dramatically increase the number of HTTP requests being made on a site with a number of users. Its definitely something to consider even if its just a minor XHR call to the server.
# Posted By Rey Bango | 10/8/07 3:03 PM
Confirmed. One of the other ideas I had - which seemed like more work - may be better. And that was this - for every JS function that does an XHR hit - have THAT change a JS variable that remembers your last action. In other words - check on the client if you haven't done anything in N minutes. The problem with this is the amount of work - as you have to ensure every function updates the time.
# Posted By Raymond Camden | 10/8/07 3:11 PM
Ray, I asked Jack Slocum, lead developer of Ext and here's how he handles this. He sets a cookie on every request to the server which manages the session timeout since the server will always have the correct timeout value. Upon every server requests, he just updates the cookie. At specific intervals, say 30 seconds, he reads the cookie to determine how much time is left on the server session and based on that determines if the user is near his timeout. He then prompts the user if they would like to extend their session and if so, makes another XHR call which subsequently updates the cookie.

The interval can be set via setTimeout() which allows you to specify a method to call at specific intervals.
# Posted By Rey Bango | 10/8/07 4:31 PM
I use a solution almost identical to that told by Jack Slocum via Rey Bango and it works nicely. It minimizes the number of extra hits on the server and keeps things pretty clean overall.
# Posted By Bill | 10/8/07 4:55 PM
We just have a CF function named "checkSession" that we call from a JS function named "checkSession" before executing any Ajax call that requires you to be logged into the site.

If the session has timed out, the callback function pops an alert message and redirects you to the login screen. If the session is active, then the Ajax call continues as normal.
# Posted By Adrian J. Moreno | 10/8/07 4:55 PM
Interesting. So if the browser is viewing a page - and does an Ajax request to change cookie X, the browser will correctly be able to see the value in JS?
# Posted By Raymond Camden | 10/8/07 4:57 PM
I haven't put this into practice, but I'm guessing if you architect your app with consistent ajax responses you could do this without pinging the server. For example, have all ajax requests always return JSON, and have a consistent format for that JSON that includes a flag for a valid session. If the session is invalid you can pop an alert, redirect the user, etc. In your security/session code, to detect whether you should redirect the user or return JSON for an expired session (redirect for regular request and JSON for ajax), you can look at the request header and see if it was an XHR request or not and act accordingly (I know Prototype puts a special header in so you can detect its ajax requests). Obviously, all this requires planning when you start coding your app, but I think it could be done.
# Posted By Thomas Messier | 10/8/07 5:35 PM
@Ray: Yep.
# Posted By Rey Bango | 10/8/07 5:35 PM
If I understand right, what you'd like to do is notify the user at some time before the server session times out. I was IMing Rey about this just now and wrote a little jQuery plugin to do this. The code doesn't actually depend on jQuery, it should work with most any client-side library. I'll post the code later this evening when I get a few minutes...
# Posted By Michael Geary | 10/8/07 6:27 PM
Guys, definitely give a listen to Michael Geary's words. He's top notch.
# Posted By Rey Bango | 10/8/07 7:32 PM
Ummm...am I missing something. Why not use the simplest solution available? An ajax request is still a http request that goes through something like "onRequestStart" on the server. So as long as you check for your session scope being valid in that method, does it matter if it's an ajax call or not? So if the ajax call comes in but the session is not valid anymore, you either redirect the user or display whatever message you want.
# Posted By Boyan | 10/8/07 8:37 PM
Boyan - the issue was partially also to warn users.
# Posted By Raymond Camden | 10/8/07 8:39 PM
@Ray: Dan Switzer chimed in and offered this alternative as well:

http://www.pengoworks.com/workshop/jquery/session_...
# Posted By Rey Bango | 10/9/07 8:41 AM
Nice. Thank you all for the comments here.
# Posted By Raymond Camden | 10/9/07 8:45 AM
@Raymond:

I'd suggest just using HTTP Response Headers to notify your Ajax requests if the user's session has expired. Using this technique you don't have to worry about making additional calls to see if a session's alive, since it's all controlled via HTTP response headers.

I've blogged about the technique here:
http://blog.pengoworks.com/blogger/index.cfm?actio...

Or you can go straight to the example code:
http://www.pengoworks.com/workshop/jquery/session_...
# Posted By Dan G. Switzer, II | 10/9/07 8:52 AM
We use a variant of the Jack Slocum one mentioned above.

We use a cookie and a JS global variable, and have the server write out the session timeout variable into another JS global.

We attach an event listener to dojo.io.bind and update the cookie with the current time when we make a request to the server, plus save the time to the global.

We use a cookie so multiple browser windows talking to the same server will all share the same last-update time. After updating the cookie we set a timer to session timeout - 1 minute, and if that goes off we check the cookie to see if we were the last window to talk to the server. If we weren't (cookie is newer than our global) we just go back to sleep. If we were (cookie is still the one we set) then we prompt the user that their session is about to expire and let them send a dummy request to keep the session alive. Being the last window to contact the server also implies that this window is the frontmost talking to that server (we don't do any of this for background polling calls, since we don't want those to keep the session alive).
# Posted By StephenD | 10/9/07 9:15 AM
# Posted By Rey Bango | 10/9/07 10:59 AM
Oh rats. My client timer solution doesn't take multiple browser windows into account. That really complicates things.

Well, the code is on the jQuery group in case it is of interest to anyone. It does illustrate a nice trick for augmenting an existing function - the $.expire function could be applied to any JavaScript function, not just Ajax calls.

http://groups.google.com/group/jquery-en/msg/e1b80...

There is one error in the posted code - the line reading "fn.apply( this, arguments );" should be "return fn.apply( this, arguments );".
# Posted By Michael Geary | 10/9/07 11:44 AM
Why time out?

Timeouts torment your users. Many users will walk away from their computers and expect to keep working the next day. Amazon keeps your shopping cart open for weeks -- they'd be throwing away $$ if they timed out your session.

If your architecture needs timeouts in order to scale, your architecture is at odds with the interests of your users. Get a new architecture.
# Posted By Paul Houle | 10/10/07 8:07 AM
Amazon keeps you cart - but it does NOT keep your login info. I have to login to actually check out and I certainly do not want my login info to stay forever.
# Posted By Raymond Camden | 10/10/07 9:09 AM
And this has nothing to do with scale - it has to do with security. (See previous comment.)
# Posted By Raymond Camden | 10/10/07 9:10 AM
Hi,

Back when I was involved in developing an heavy AJAX based .NET Web application for a leading Media company in the 1.1 days, this idea was added to the scope as a 'very nice to have feature'. And it has become one of 'The fundamentals things to do for an AJAX based Web Application that enforces Authentication'. We use a similar approach with the Session ping interval value being set to 19 minutes (your_session_timeout_minutes - 1 minute considering other delays). This way it does less-frequent pinging - 3 per hour which is MUCH MUCH ACCEPTABLE than having the Web application expire every 20 minutes.

Hope this helps!

Sheriff
# Posted By Drivestream Sheriff | 10/10/07 11:02 AM
Drivestream: I may not be understanding you right. I get that you only ping once every 19 minutes. But are you keeping the session alive? Why do you say the web app expiring at 20 minutes is unacceptable? Doesn't security worry you?
# Posted By Raymond Camden | 10/10/07 2:58 PM
Raymond:
Just a thought here. If all of your Ajax calls go through the same function at the end (which i assume it is), this problem shouldn't be too hard to solve.
This is how I would approach it:
1. When a person logs in, the javascript served carries a timeout variable
2. As soon as they land on the page, you start counting down with the timeout variable (simple as setTimeout(var_timeout, logout))
3. On every Ajax call, you reset the setTimeout
4. Every Ajax call will return a flag of if the session has in fact timed out or not on the server side
5. Step 4 is literally to protect you from a person trying to screw around with your script (let's say using firebug?)
6. If the Ajax call returns the flag saying session timed out on server side, you do a self.location and take them to the login page again.

Again, just a thought. I'm sure it's not 100% perfect. :)
# Posted By Simon Jia | 10/10/07 3:47 PM
I've tried out the cookie method and it seems the best to me.

I use the application.cfc onRequestStart to set a cookie containing the date/time, so any activity from a user will get the cookie stamped.

Then, on the HTML returned I provide JavaScript with the current date/time to compare against the clients date/time and find the offset using Date().getTime() in ms. Useful since everyone's clocks aren't synced. It's then just a matter of comparing the current time (accounting for the client/server offset) against the cookie time.

So if there's an Ajax request it'll update the cookie and thus the timer on the HTML page. That way there's no need for any type of "ping" service or making sure that all Ajax code resets the timer.
# Posted By David Boyer | 10/12/07 3:53 AM
@Paul: Why time out?

As mentioned, security. Even ignoring things like public terminals and credit cards etc. some industries have requirements about leaving users logged in; if a user walks away from their desk without logging out someone's medical records might get changed.
# Posted By Dave | 10/12/07 10:19 AM