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.
Archived 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.
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.
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.
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.
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.
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?
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.
@Ray: Yep.
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...
Guys, definitely give a listen to Michael Geary's words. He's top notch.
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.
Boyan - the issue was partially also to warn users.
@Ray: Dan Switzer chimed in and offered this alternative as well:
http://www.pengoworks.com/w...
Nice. Thank you all for the comments here.
@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/...
Or you can go straight to the example code:
http://www.pengoworks.com/w...
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).
This post on Ajaxian might help:
http://ajaxian.com/archives...
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/gr...
There is one error in the posted code - the line reading "fn.apply( this, arguments );" should be "return fn.apply( this, arguments );".
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.
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.
And this has nothing to do with scale - it has to do with security. (See previous comment.)
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
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?
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. :)
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.
@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.
Has anyone had any issues retrieving Session variables with an ajax call if you're using Fusebox 5.5?
If I place another Application.cfc in the same directory as the web service component I can get closer and return the whole Session object but I still can't reference Session.oObject.Name for example.
I would rather not return the whole Session object since this could be a security risk. Thanks to anyone that can help!
I would like to simply query the server as to how much time remaining a session has left. I'm not concerned about extra http requests to the server because the number of users will remain pretty small and the client only needs to poll for the session every #sessionTimeoutInterval - 15 mins# hours and can be optimized for less.
How do I access this info about session time remaining? Where does cf store it? How do I query that without touching the session, thereby resetting the time remaining?
@Danny:
There's no reason to be doing this via AJAX. You can just create a JS variable w/a date/time stamp of when the session will expire. You can then just check the current time and compare it to the time when the session expires.
You can do something like:
var expiresInMs = 6000;
var sessionExpires = new Date();
sessionExpires.setTime(sessionExpires.getTime() + expiresInMs);
Where the value of 6000 is the number of milliseconds until the session expires. By using the user's clock and just adding the appropriate amount of time, you'll have a pretty accurate idea of when the session expires (there's a little bit of offset in the transit of the HTML and running of the page, but it'll be very close to the real time.)
You could also use the expiresInMs to do something like:
setTimeout(function (){
alert('You session has expired');
}, expiresInMs);
Or
setTimeout(function (){
alert("You session will expire in 5 minutes.");
}, expiresInMs-(5 * 60 * 1000)); // 1000ms * 60 = 1 minute
The only place where this does *not* work, is if you have users that might have multiple windows/tabs open where page hits can be resetting the time when the session expires.
@Dan Thanks for the reply.
My issue is that I have a very ajax heavy page, and any one of those being triggered would reset the session time remaining. Ok, I can manage that with $.ajaxSetup, but there are also popup windows. And iframes. It's wild.
So really the issue I am looking to avoid is erroneously alerting people that their session is going to expire because the client isn't informed of everything going on. The server ultimately manages the session, so that seems to be the best place for me to get the info.
It is growing more apparent that what I really want to do is impossible with coldfusion, or at least very difficult. It's like quantum physics. You can't observe something without changing it somehow.
Posts like this don't encourage me: http://www.bytestopshere.co...
Thinking:
Each session is given a new UUID. Just another property of the session.
OnRequestStart writes a temp file to a web accessible directory with name the same as the session UUID. The temp file is a json file, and it contains the session time remaining. The client is aware of this file. Periodic ajax requests to get this file retrieve the session time remaining without CF knowing about it.
Am I crazy? I can't tell. Maybe the cf event gateway can intervene here too, for cleanup tasks etc. I'm guessing in my case I will have dozens of open sessions at a time.