Ask a Jedi: cflogout, session variables, and the back button

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!

Comments

Dave Ferguson's Gravatar 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
# Posted By Dave Ferguson | 1/1/09 10:43 PM
Phillip Senn's Gravatar 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.
# Posted By Phillip Senn | 1/2/09 7:50 AM
Raymond Camden's Gravatar 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.
# Posted By Raymond Camden | 1/2/09 8:21 AM
Steph's Gravatar 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
# Posted By Steph | 1/2/09 10:05 AM
Raymond Camden's Gravatar 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.
# Posted By Raymond Camden | 1/2/09 10:25 AM
Ben's Gravatar 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!
# Posted By Ben | 2/22/09 4:24 PM
Raymond Camden's Gravatar 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.
# Posted By Raymond Camden | 2/22/09 4:59 PM
David's Gravatar 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?
# Posted By David | 4/6/09 10:23 AM
Raymond Camden's Gravatar There are META tags that also relate to caching. I can't remember them offhand, but you can try using them as well.
# Posted By Raymond Camden | 4/6/09 10:11 PM
Phillip Senn's Gravatar <cfheader name="Expires" value="#GetHttpTimeString(Now())#">
# Posted By Phillip Senn | 4/7/09 12:56 PM
David's Gravatar 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())#" />
# Posted By David | 4/7/09 1:15 PM
Raymond Camden's Gravatar 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.
# Posted By Raymond Camden | 4/7/09 1:18 PM
Jody Fitzpatrick's Gravatar 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?
# Posted By Jody Fitzpatrick | 6/14/09 10:47 PM
Raymond Camden's Gravatar 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.
# Posted By Raymond Camden | 6/15/09 10:19 PM