Steph has a few questions/concerns about cflogout, sessions, and the back button. His email to me was:
Hi Ray, What's the best practice for logging out users using cflogin and cflogout? I'm using CF7, and I cant figure out how to workaround the following issues listed in livedocs:
Caution: If you use web server-based authentication or any form authentication that uses a Basic HTTP Authorization header, the browser continues to send the authentication information to your application until the user closes the browser, or in some cases, all open browser windows. As a result, after the user logs out and your application uses the cflogout tag, until the browser closes, the cflogin structure in the cflogin tag will contain the logged-out user's UserID and password. If a user logs out and does not close the browser, another user might access pages with the first user's login.
Also....
In many cases, you can effectively end a session by clearing the Session scope, as shown in the following line. The following list, however, includes important limitations and alternatives:
<cfset StructClear(Session)>
Clearing the Session scope does not clear the session ID, and future requests from the browser continue to use the same session ID until the browser exits. It also does not log the user out, even if you use Session scope storage for login information. Always use the cflogout tag to log users out.
Well, I use cflogout, but if you use the back button you are still able to access the protected pages, and session variables are maintained.
What's the best way to completely log a user out (kill session variables, etc) while keeping the browser open?
There's a lot going on there, so let me try to pick it apart. First off, the issue mentioned in the live docs quote above (direct link here) relates to the fact that ColdFusion's CFLOGIN system will automatically connect to any web server level security enabled. My readers know that I've been a bit ticked at CFLOGIN for some time now. I once lost a whole day trying to debug an issue with BlogCFC that ended up being related to this. There is no way to disable CFLOGIN's ties to web server security. That means trouble if you don't want to use it. This is why I do not recommend using CFLOGIN anymore. I still use it in my OS applications but I'm slowly rolling it out. (The last update to Soundings removed it.)
So with that in mind - I'd just stick to simple session variables. That removes any problems with the web server level security. It's no longer an issue. But then that leaves your last issue:
Well, I use cflogout, but if you use the back button you are still able to access the protected pages, and session variables are maintained.
Technically, the session variables weren't maintained. What you are seeing is simply the browser's cache being used to redisplay an old page. The session variables on the server were really cleared.
So how do we stop the pages from being cached? You have to remember that the browser, not you, is the ultimate judge on what it will and will not cache. We can tell the browser to not cache, but that doesn't mean the browser won't ignore your request. The user could also just save the HTML to the desktop. It would be dumb for a user to save a sensitive page of credit card information to their desktop, but I bet we all know a few users who would do that.
With that in mind then, the code to tell the browser to not cache is rather simple, and is described at the ColdFusion Cookbook entry: How can I prevent a browser from caching my page?
<cfheader name="cache-control" value="no-cache, no-store, must-revalidate">
<cfheader name="pragma" value="no-cache">
<cfheader name="expires" value="#getHttpTimeString(now())#">
Steph, hope this helps!
Archived Comments
There is also another issue with caching that Ray does not mention. There are TONS of client installed 3rd party caching tools to speed up internet browsing. A vast majority of these will actually ignore the http header settings and cache anyway.
I have also personally ran into issues with network based caching systems. These do just the same as client software but on a larger scale.
I don't really have a good way around these so sorry for that. I just wanted to bring them up in case someone else might.
--Dave
I seem to remember you saying to not use structClear(session) as well, but to use structDelete instead.
Something about how structClear will delete the sessionid and cfid and cftoken.
But that was years ago.
Yes, that was true years ago, but not since CF6 I believe.
Well wait, I stand corrected. :) It removes cfid, cftoken, and sessionid. It looks like structClear is still "naughty". Everything worked fine though with this sample code:
<cfif structKeyExists(url, "clear")>
<cfset structClear(session)>
</cfif>
<cfparam name="session.hits" default="0">
<cfset session.hits++>
<cfdump var="#session#">
I could clear the session and still add values to hits. But if I had written any code to depend on sessionid, it would fail. Oddly, urltoken, one of the 'builtin' session variables, was also nuked on the FIRST hit with clear=1 in the URL, but returned after clear was removed. So CF 'fixed' up one built in var but not the others. Also, the values in URL token matched the CFID/CFTOKEN I had before.
So I'd probably not recommend doing a struct clear on session now.
Thanks Ray!
You mentioned that the session variables are cleared on the server but maintained in the cached page. I logged out and then used the back button until I reach the first page that I accessed after logging in. When I do a shift-refresh of this page, I am then able to proceed to other pages as if I were logged in, and the session variables are still available per the debug info.
I changed my app so that the login page points to a redirect page that points to the first page of the app. This seems to solve the shift-refresh problem.
Are there any plans to fix the cflogin issue with the next CF version?
Steph
Actually no, I said the session variables were NOT maintained. You have to remember that there is a difference between the actual variable, session.name, and the _display_ of that on a page. If I logon, and end up at a page that does #session.name#, and the value is Ray, I can keep that web page on my computer for years. (If I don't reload.) The word "Ray" is still there, but the session variable timed out long ago. So when you see the browser get the page from cache, it is just getting the HTML from it's cache, not the actual session variables.
You mentioned you were able to shift-refresh on a page. Was that page the result of your login? (You said you went to the first page.) If so, what you see is the browser re-POST-ing the form details from the login, so in essence, you logged in again.
Making your login page do a cflocation does help because then the user can't reload the page to relogin.
This is not broken, Steph. It's just the nature of the web, browsers, and your cache settings.
thanks Ray! I've always enjoyed learning from your writings and posts. I have a couple of related questions that you might know the answer to:
1. how to force CF to end a session on browser close? the session structClear is working great for me, but sometimes the user just closes it.
2. keeping a session variable consistent between https and http pages. Sometimes CF seems to come up with a new session ID when going from HTTP to HTTPS and back. Undoubtedly, its my misunderstanding of proper usage ... but I figured a jedi might know.
thanks again for all your good work and sharing!
1) To be clear - you can't END a session. CF ends a session when it has been inactive. You can, however, make it so when the user closes his browser and comes back, he gets a new session. Just use J2EE sessions in the cf admin. There are other ways, but that is the easiest.
2) That's because, technically, it is a different site. If you are using APplication.cfc, try using setDomainCookies=true. I believe it changes the cookie from foo.main.com to *.main.com. I always forget the exact change so forgive me if that is wrong.
If the user logs out, I don't want them hitting the back button to see the previous page. So I'm use the cfheader code as outlined in this post.
Firefox and IE seem to respect this very well, however, Safari does not. In other words, I can hit the back button in Safari (3.2.2) and still see the previous cached page instead of the app redirecting to the login page (as Firefox and IE do so well).
Has anyone else experienced Safari not obeying cfheader? If so, how did you make Safari take orders?
There are META tags that also relate to caching. I can't remember them offhand, but you can try using them as well.
<cfheader name="Expires" value="#GetHttpTimeString(Now())#">
Raymond, the meta tags don't make a difference for me.
Phillip, I'm already using that code:
<cfheader name="cache-control" value="no-cache, no-store, must-revalidate" />
<cfheader name="pragma" value="no-cache" />
<cfheader name="expires" value="#getHttpTimeString(now())#" />
Hmm. Well, at the end of the day, this may be a browser issue. Just because the server says, Dont Cache, and the HTML says, Dont Cache, it doesn't mean the browser has to follow the rule.
Hey Ray
I use a really different method when it comes to processing security, I do however use cflogin and cfloginuser but I also create a UUID and log the ip into a database and if the UUID doesn't match the ip I send them to the logout processor. Is that a smart way to process security?
Hmm. Seems kind of nice. I'd probably store the info in the app scope though. Don't see a need to persist it like that unless you have a cluster.
I'm looking for a way to modify the CFID and/or CFTOKEN cookies set by CFLOGIN. These cookies expire 30 years after login. I'd like them to expire when the browser closes. What should I change?
cflogin can be set to tie to the session scope, which means it will end when the session ends.
How would you do that?
I'm developing a web based app & I'm making use of sessions, no cookies, for security. It works fine & users can successfully logout. However, & this is a pretty serious issue: if you click on the back button you are still able to access the protected pages.
I've pretty much tried the CFHEADER tags on each page.
<cfheader name="cache-control" value="no-cache, no-store, must-revalidate">
<cfheader name="pragma" value="no-cache">
<cfheader name="expires" value="#getHttpTimeString(now())#">
This really helped for Firefox3.0.12 & for Netscape Navigator9.0b3 BUT NOT FOR Opera 9.64 & very importantly NOT FOR Internet Explorer 7.0.6001.18000.
A major concern is that majority of web users use IE!
Please can anyone provide a tried & tested permanent solution please? It'd be really appreciated. Thanks
At the end of the day, the browser, not you, decides how to cache content. Those header tags tell the browser to not cache, but the browser can ignore it at will. Not only that, I can simply save your pages to the desktop if need be. Have you tried perhaps the META tag as well? While it has the same issue, it may work in IE better. Just note you can't depend on it.
Thanks ray, I tried them but will again. However I noticed yahoo had the same issues years back but not anymore. I'm cerain theres a solution out there somewhere. I'm certain the session variables aren't stored after logout though so it's a cache issue & interestingly upon page refresh after the use of the back button, the page access is denied. I guess this might provide a much better clue. Thanks
I included the following but I noticed no difference
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="Mon, 07 July 2009 00:00:00 GMT">
Then I think you may be out of luck. Again - it really is a client side issue that you can _never_ be 100% sure of. You could include Ajax code that pings the server, notices a session timeout, and pushes the page away, but even that can be surmounted if the user disables JS.
I thought of coding to [force] clear the browser cache afterward but the concern is other user data. I wouldn't want that to happen now. I was just thinking: what if the app runs in a browser window w window which does not show any buttons?, you know like the one for typing in posts(this particular one on this site)? I guess that will pretty much work. Does coldfusion have any specific tags or any code for that? Thanks
That's all client side JavaScript, opening a window with no chrome. The closest thing ColdFusion has is cfwindow.
OK, CF8 stuff.Just thinking: is there something like the cfscript variant of the javascript window.open ?Thanks
Nope - closest is cfwindow.
Alright, thanks
I have always used session variables as a means to determine who is logged in... now that most users are familiar with multi tab browsers, I have an issue with users logging in with different user accounts in the same browser window (but separate tabs.) As we know session vars are shared between the tabs causing grief in this situation. Making matters worse, what if the user opens a third tab and copy/pastes the url. This really limits the use of session vars.
Anyone have any thoughts on managing session vars in multi tabs in the same browser window?
You have to remember that a tab is nothing more than another request. The server has no way to differentiate one tab request from another. The only solution would be to not use cookies for your sessions and use URL params, but even then, if the user opens the new tag and pastes in the url with the params, you are in the same situation.
In my opinion, this is _not_ a limitation of session vars.
It's too bad there is no way to identify browser tabs... and I agree it's not a limitation directly on session vars as session vars have not changed. The environment of a single browser session has changed with the introduction of multi tabs.
Without a way to identify tabs, IMO the practical use of session vars is greatly compromised.
Any thoughts are appreciated. - Tim
But I think here you have a unique situation - users going out of their way to use the application incorrectly. Users will always be...well, users, but personally, I've never heard of folks doing that.
There is one simple solution - if a user is logged in, don't let them log in again. Tell them as much and then provide a link to the logout page.
I think users may be attempting efficiency with multi tabs not realizing the application was not designed for multi tabs.
Yes - only allowing one log in per window session will provide as a solution for the multi tab multi user issue. However there are many more potential situations other than just the log in.
Guess my point is CF developers should consider the potential issues with browser tabs when using session vars... (still wishing tabs could be identified.)
Thanks Ray
Hi Ray,
I have a form page, an action page and a confirmation page.
I submit the form page...takes me to the action page where I validate the fields...the fields are NOT valid..I place the data & the error message in session and redirect(cflocation) to the form page. In the form page I store the session variables(err message and bad data) in local variables, destroy the session variables and use the local variables thereafter.
Now I correct the values and submit the form again...goes to action page...validates and then redirect to the confirmation page.
Now if I use my browser back from the confirmation page, it takes me to the form page with the validation error message and bad data...I understand that the IE 7 browser is using the cache to get the data..but I tried using cfheader and META tags to avoid pulling from cache...still the browser pulls from cache...this doesnt happen in Firefox
Any thoughts on how this can be resolved?
As just an FYI, I normally recommend against multiple pages like that. I'd probably use one page that just renders based on what the current state is. But that's a different story. :)
So normally the cfheader thing should work for IE. Can you share the code you used?
Ray,
I am using the following code:
<cfheader name="cache-control" value="no-cache, no-store, must-revalidate">
<cfheader name="pragma" value="no-cache">
<cfheader name="expires" value="#getHttpTimeString(now())#">
<META HTTP-EQUIV="expires" CONTENT="-1">
<META HTTP-EQUIV="pragma" CONTENT="no-cache">
<META HTTP-EQUIV="cache-control" CONTENT="no-cache, no-store, must-revalidate">
Also, I am interested to know if there is a specific reason (like its a bad practice..) on why you are against using multiple pages. It will be a learning for me.
Thank you for your time!
That code looks fine to me. At this point I'd just blame IE. That doesn't help you though.
In general, I don't use an 'action page' for a form. I use a self posting form. Here is an example I just wrote. It may not work - I didn't actually run it I just did it from scratch:
http://pastebin.com/iC0JUAvm
You can see how the form posts to itself. Code on top does quick verification (the form has one field so its simple). If the form is ok (the one field is ok), then we set a flag to not show the form.
This is just an example though.
Thank you so much and Sorry!
I thought I replied already but when I checked the site for a response from you....thats when I realized I didnt even reply to your message...very sorry abt that.
Thank you for your example, I am aware of "posting to the same page" method...
Sorry if I was not clear with my question, I wanted to know why you thought the "posting to same page" method was better than the multiple pages method?...these are facts only experienced people can explain....not the books :)
How about killing all sessions for a single CF application, just once? I need to "refresh" the session scope, but not for the entire server, just the one application. (Using application.cfc, BTW.)
I don't believe it is possible. CF9 added applicationStop(), which 'resets' an application. When run, the next hit will execute onApplicationStart. But it doesn't reset sessions.