ColdFusion SESSION Is Always Created Even If OnSessionStart() Fails
Every now and then, in very rare, seemingly random ways, I'll get funky SESSION errors in my ColdFusion applications. Generally, I'll get an email (I email myself errors) that says something like:
Element USER is undefined in SESSION.
This error will be in some part of the application that the user could not possibly have gotten to had they not already been logged in and clicking around. Meaning, there is no way that the SESSION object would not have been fully initialized at that point in the code.
I have never been able to debug this problem. In fact, I've never even been able to duplicate the error after I see it come in. Last night, however, I was thinking about session management and it suddenly occurred to me that I am not exactly sure what happens if something in the OnSessionStart() event handler fails. Meaning, if the OnSessionStart() event handler throws an error, does the session get created?
To test this, I set up a simple Application.cfc component with a faulty OnSessionStart() event handler:
<cfcomponent output="false" hint="I define application settings and event handlers."> <!--- Define application settings. ---> <cfset THIS.Name = "SessionTest" /> <cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 5, 0 ) /> <cfset THIS.SessionManagement = true /> <cfset THIS.SessionTimeout = CreateTimeSpan( 0, 0, 0, 20 ) /> <cffunction name="OnSessionStart" access="public" returntype="void" output="false" hint="I fire when a new session begins."> <!--- FORCE FAILURE AFTERWARDS. ---> <cfset SESSION.Foo = Bar /> <!--- Set up session variables. ---> <cfset SESSION.LoggedIn = false /> <cfset SESSION.ID = 0 /> <cfset SESSION.Name = "Guest" /> <!--- Return out. ---> <cfreturn /> </cffunction> <cffunction name="OnRequestStart" access="public" returntype="boolean" output="false" hint="I fire when a new request begins."> <!--- Check to see if the user is logged in. ---> <cfif NOT SESSION.LoggedIn> <!--- ... do something ... ---> </cfif> <!--- Return out. ---> <cfreturn true /> </cffunction> </cfcomponent>
As you can see, the very first line in the OnSessionStart() event handler makes reference to an undefined variable, Bar. This will prevent the SESSION scope from being fully initialized, which may or may not cause problems for the OnRequestStart() event handler which makes direct use of expected session variables.
When I run the above code, I get the following error:
Variable BAR is undefined.
This is to be expected. However, when I run the page a second time, I get this error:
Element LOGGEDIN is undefined in SESSION.
Now, the execution code has been able to progress down to a point where theoretically, the SESSION scope should have been fully initialized. So, the take away here is that the SESSION scope gets created before the OnSessionStart() is called. This makes sense if you pause for a moment - if the SESSION scope weren't created yet, we wouldn't be able to initialize it in the OnSessionStart() event handler. The byproduct of this, as we are seeing though, is that even if the OnSessionStart() event handler fails, subsequent page requests by the same user will skip the OnSessionStart() method as the session has technically already started.
I am not sure that this is the problem that I am running into as it is not an error that I have ever been able to duplicate; but, at least this gives me *some* direction to look in the next time it happens.
Want to use code from this post? Check out the license.
Maybe they should have named it onSessionAlreadyStarted() for additional clarity.
I think, if the OnApplicationStart() can return a boolean to kill application boot-up and OnRequestStart() can return boolean to kill the page request... then, it would also make sense that OnSessionStart() would return a boolean to kill the session startup. I wonder why that one is the odd man out?
I would be interested in seeing the code you use to have all site errors emailed to you. :-)
I expect that is because session is controlled at a lower level than the ColdFusion tier. I do not have any real information about it though and agree it is a curiosity for the ages!
This is one of the differences between CF and Railo. Sessions are not constructed until first use in Railo, but CF constructs them immediately. So, in Railo, onSessionStart does not run until you actually try to set a value into session scope, but in CF it's called on the first request.
But, if the OnSessionStart() in Railo failed on the first line, would OnSessionStart() be called again on the next page request (to the same template)?
Shouldn't you have then gotten emails for the onSessionStart failing? That would have made the "user is undefined" error easier to figure out.
@Steve, I imagine it would be something like this:
(no idea if this will display properly)
<cffunction name="onError" returntype="void" output="true">
<cfargument name="exception" required="yes"/>
<cfargument name="eventName" type="string" required="yes"/>
<cfif StructKeyExists(arguments.exception.RootCause,"type") AND arguments.exception.RootCause.type EQ "coldfusion.runtime.AbortException">
<cfif EventName NEQ "onApplicationEnd" AND EventName NEQ "onSessionEnd" OR len(url.error)>
<cfmail to="#application.settings.email.error#" from="#application.settings.email.webmaster#" server="#application.settings.email.smtp#" subject="Error: #arguments.exception.rootcause.Message# " type="html" spoolenable="yes">
<cfoutput>Error occurred: #Now()#<br /></cfoutput>
<cfdump var="#arguments.exception#" label="Exception"/>
<cfdump var="#cgi#" label="CGI Variables"/>
<cfdump var="#form#" label="Form Variables"/>
<cfdump var="#url#" label="URL variables"/>
<cfif StructKeyExists(arguments.exception.RootCause, 'ErrorCode') AND arguments.exception.RootCause.ErrorCode EQ "PageNotFound">
i have just made a simple test app with just Application.cfc *WITHOUT onSessionStart() method in it* (only with sessionmanagement=true in the constructor part, and i could cfdump session in onRequestStart method...
cf must be creating a session as soon as it sees sessionmanagement=true then?
Yeah, but I never get those... so many there is something that fails in the session init as well as in the OnError() event method. That's why this bugger is so hard to track down!
hmm... even with the constructor set to THIS.sessionmanagement = false, i can still dump the session structure, albeit empty one this time...
Ben, what may be causing that error is a user walking away from a browser that he's logged into your app with and then returning later, with his session timed out, and then attempts to reload the page or follow a link that uses other session info (maybe there are URL parameters denoting that certain logged-in-only code should execute).
So to clarify:
1. User logs in. Your app sets session.user = etc.
2. User does some privileged stuff.
3. Some dame shows up.
4. User returns 2 hours later and clicks a link or reloads the page.
5. Because your has content that doesn't require the user being logged in, onSessionStart doesn't guarantee a session.user value, and that error occurs.
Two possible fixes:
1. Have session.user populated with a default value ( in onSessionStart ) and check for a non-default value instead of just accessing the variable in any code that needs it.
2. Run a centralized isLoggedIn method at the top of any privileged code to detect the absence of any of the session vars you use and force a relogin to catch cases like this.
OMG that was horrible English.
I've considered that, but the OnSessionStart() *should* run before any request handling when the user comes back and clicks the link. As long as the page flow is the same - session handling followed by request handling, it should be good.
Not to say that what you are suggesting isn't the scenario... just nothing inherently bad about it.
@Steve, @TomK - I've been replacing my custom error handler/emailer/logger functions with the 3 logger objects available in Mach-II 1.6+. http://greatbiztoolsllc.trac.cvsdude.com/mach-ii/wiki/IntroToLogging
The logging facility can be made even better by adding ColdSpring's AOP. http://coldspringframework.org/coldspring/examples/quickstart/index.cfm?page=aop
Right Ben, I was saying that if you application allows some content /functionality to be served without logging in, and therefore you only ever populate a session.user variable when someone DOES log in, and then once logged in, with session.user populated, a session times out, and the user is already on a URL that no longer checks/forces a login, and then tries to reload that page...
The onSessionStart method will still execute normally on that initial post-timeout request, but since they're past the login barrier your code may try to reference a session.user variable that, because they've foregone its population by not re-logging in, will not be there, generating that exact error.
If you're populating session.user in your onSessionStart method, then for sure what I'm saying might be happening is NOT happening.
If you're not populating session.user in onSessionStart, and are populating it in some method that only gets called when they login, this may very well be your issue.
I suspect that something like what you are saying is happening (people are walking away from pages and the active session timesout). But, what I can't figure out is where / why that is causing a problem :( It's so frustrating.
Steve, you can have the ColdBox Framework e-mail captured errors.
Search for BugTracer
I had that problem on a site that used frames. It used to run with a app.cfm but I changed it to a cfc and got those random errors after session timeout. It took me a while to realize that the actual frameset was a cfm page and only one of the pages that the frameset requested initiated the cflogin phase (after the timeout). The curious thing was that even if I reloaded a frame that displayed "Element USER is undefined in SESSION" AFTER login, the error remained. Even more strange, if I in that situation reloaded the entire frameset, some frames would have that error.
I "fixed" it by putting
<cfif GetAuthUser() NEQ "">
but I still don't know why the system got out of sync. The frame that had triggered the login worked fine, but the others did not. A fresh login from a completely dead session always worked fine.
YES! Such a frustrating problem! The frameset is interesting - I wonder if is has anything to do with simultaneous requests? Imagine one person launches two requests at the same time, is is possible that only one of them fires the OnSessionStart() event (this is what happens), but, the one request that didn't gets to the OnRequestStart() event first, before the other request is done initializing the session object?
Thank you for you comment! I think it lead me to a moment of great insight and inspiration as far as handling session management and asynchronous page requests:
You the man!
Shouldn't the page containing the frames call onSessionStart before the frame loads? The frame wouldn't make it's request until the page containing has flushed it's content to the browser, at which point onSessionStart should have already finished.
This is true, if the Frameset page was a .CFM file. In the demo (see note above), I had to change it to a .HTM file to prevent this from happening.
Sorry. I should read before speaking.
No worries - my posts are mad long and people tend to skim them :)
I also get similar errors at least two or three times a day. It happens with my session.userID. But the location of the errors happen in places that a user should have never been able to get too. Here is a stripped out example of my problem.
<!--- make sure userid is defined --->
<cfiif NOT isDefined("session.userID")>
.... <cflocation url="/login.cfm" addtoken="false" />
<!--- user is logged in --->
Hey user #session.userID#!
<cfinclude template="DisplayProfile.cfm" />
Then within DisplayProfile.cfm I have a query.
<cfquery name="GetProfile" datasource="#application.dsn#">
WHERE UserID = #session.userID#
The error will occur on line 4 of DisplayProfile.cfm saying userID is undefined in session. But I can see from the error GeneratedContent that 'Hey user 1034!' displays. It makes no sense to me and I have not been able to duplicate the problem.
This doesn't happen every request and seems completely random. I am not using frames but I do generate a lot of traffic. So I totally understand where your coming from Ben. Keep working on a solution ;)
You feel my pain :D This error is mind boggling!
I'd thought onRequestStart and onRequest were half supposed to be used mutually exlusively?
Is there some advantage to having that code in onRequest instead of at the tail of onRequestStart?
I think of them as having two different purposes:
Initializes the request including REQUEST-level variables and any logging and security setup.
Executes a template. If you don't use this, Application.cfc implicitly executes the requested template; but, you can override this to have logic about which template gets executed.
My OnRequest() method might take advantage of security settings set up in OnRequestStart() when determining which template to include.
For my sites, I try to use OnRequestStart as a predefiner for any variables I might need to have or already know (mostly session information.) to make sure I can get to whatever I'm trying to hit.
My OnRequest would actually be the output I'm going to send to the browser. Personal preference I assume?
After reading over the docs, it seems like onRequest has a big limitation / road block.
"Implement this method only if the following [is] true:
The directory, and any subdirectories affected by this Application.cfc contain CFM files and do not contain any CFC files that are intended to be accessed as web services, using Flash Remoting, or using an event gateway."
Because it seems like you can do anything you'd normally do in onRequest in onRequestStart (what I've always done, and I've never felt like I missed out on anything), and because of the "no services allowed" blocking of onRequest, is there an argument to use onRequest for any new applications going forward?
Ben, it sounds like you use it more as a logical / organizational separation because that division makes sense to your head. Is that right or am I missing something?
You can do CFC remote calls with Application.cfc (NOTE: they are ONLY talking about remote calls, not general .CFC usage), you just have to test for it and delete the OnRequest() method from OnRequestStart(). It's a bit of a nuisance, but once you do it, it's done.
It's not just a logical separation - the OnRequest() is meant to handle template execution. The OnRequestStart() method doesn't really have anything to do with template execution. In fact, you can return FALSE from the OnRequestStart() and NO template will be executed afterwards - it will prevent template execution from taking place at all; but, the method itself doesn't really handle it.
It's possible that I'm just being oblivious here and totally misunderstanding the concept.
But when you say "handle template execution"...
I can see that maybe some people have the pre-existing preference of wanting to check for authenticated credentials, and then if they exist "execute the template" ( meaning cfinclude it ) and if they don't forego the inclusion of that template ( usually the URL pointer ) and execute some other template instead ( login page ). And they really like that approach and to see it in code.
But am I wrong, or is it just as easy to do that in onRequestStart without any the limitations imposed on the rest of your application ( like the workaround you mentioned to enable remoting calls )?
if( not authorized )
cflocation url = 'login.cfm'
It seems like doing it that way would be
1. less code
2. less involvement in the management of 'template execution' ( freeing you up to focus on functionality instead )
3. one less application event to worry about
I'm really trying to wrap my mind around the advantages of manually orchestrating the template execution ( in onRequest, as you could just as easily do it in onRequestStart and abort or return false like you pointed out), and to see if there are benefits I'm not reaping so I can start to.
I am certainly not a master architect, but here are some things that I have do / have heard:
* Ideally, you don't want to CFLocation to your login page because that will return a 200 status code. Ideally, if someone requests a page and they need to login, you would return a "401 Access Denied" header and then include the login page.
I am not saying that I do this in my apps, but I believe it is technically the more "correct" way to signal to the browser that the request page is NOT the page that is actually being returned (something I learned at a REST architecture seminar).
* I generally force all my template requests to go to the index.cfm page (front controller). As such, my OnRequest() method turns around and includes the "index.cfm" template no matter what was actually requested.
There are exceptions to this, that I do handle in the OnRequest() event (ex. some directories for testing that are always "open").
As far as less involvement in the management of 'template execution' ( freeing you up to focus on functionality instead ).... I don't think template management should be a neglected part of the application. If you have work flow around your template execution, then it is a critical part of the page request life cycle; I don't think template request execution should be thought of as a secondary feature.
And, of course, you don't always need it. You need if you need it for a given application. I tend to include it by default because I tend to insert some sort of template execution management at some point, so it's nice just to have around.
Great food for thought, Ben.
It's always really interesting to see why certain developers do things differently than you do, rather than just seeing that they do.
OOC, wouldn't you still have to manually orchestrate the http status codes in either scenario with cfheader? Or is that easier with onRequest?
If you were to try and include the requested template in the OnRequestStart() method and you did not have an OnRequest() event method, ColdFusion would end up executing it twice - once for your manual include and once for the implicit include that ColdFusion performs on any requested template in the absence of OnRequest().
If all of your requests go to the same file (ex. index.cfm) then theoretically, you could handle the status code in the OnRequestStart() and change the event variables and use implicit inclusion. But now, you are depending on convention, which might not be the best.
Overall, not using the OnRequest() event method I think just makes things more difficult, not less.
Simultaneous .cfm frames isn't the only time this would happen.
When a user has more than one of your app's pages open in separate tabs, and has their browser configured to re-open their previous session when they launch it...
Depending on the caliber of the user's hardware ( and maybe the browser itself ), I think the browser will make one simultaneous(ish) request for each tab you had open.
I just realized that I personally do that all the time. Have more than one tab open in the same app, leave and close the browser, and then when I re-launch it it's the same deal. It re-opens all of them and makes real requests for each one. Just a thought.
Yeah, with tabbed browsing and people *still* double-clicking web links, I can see where this would happen.
Sorry to dig up an old thread, but was there any usefull outcome to this illuminating discussion? :)
The only thing I have been able to think of is to have a SESSION-based lock in the OnRequestStart() to ensure that some session initialization variable exists.
I was just looking at my automated emails that dealt with the Session is invalid issue and I had a thought occur to me.
Could it be that, the person walks away and comes back two hours later. Then when they click submit, a link, etc. the cfid and cftoken are read from the cookies by the server. The server sees CFID and CFTOKEN and processes that as "Hey there is a session already out there!" therefore doesn't hit onSessionStart?
That make any sense? I'm just throwing out ideas here. I'm still scratching my head on this one.
If the session has timed out, the new session will be created, but it will use the same CFID and CFTOKEN of the previous session. But, it will invoke the onSessionStart() event handler.
Good thinking though, it's definitely a hairy situation.
I just had this problem; sure enough the issue was in the onSessionStart(). I've been collectively adding to my understanding of how the application starts up and how requests are run through.
At first, the error was seemingly in the onRequestStart since it was referring to a Session variable that didn't exist. Come to find out, in my onSessionStart I was referencing a variable that only gets created later on in the startup. This was for a log. Moving the log to the end of the onSessionStart() method resolved the issue.
Ben, I'm seeing EXACTLY this issue, and it's as maddening as hell (largely because it feels impossible to debug).
In my app, onSessionStart() instantiates a few objects stored in the session scope and then referenced within onRequestStart().
When a user's session has timed out, onSessionStart() should run, restting their permissions to those of a "not logged in" user. If they're trying to access a restricted page, subsequent code bombs the user back to the "Please login" page. Ordinarly, this works fine.
I find with infuriating frequency (several times a week) that onRequestStart() is being processed as though onSessionStart() has completed, but the session-scope variables that _should_ have been instantiated _aren't_.
This results in "Element BLAH is undefined in SESSION", just as you have been seeing. It appears to always be when a user has logged in, and then allowed the session to time out, but the code that should redirect them to the /login part of the site doesn't appear to run.
This has been stumping me for months now :(`
you are controlling Sessions in the onRequestStart
<!--- Check to see if the user is logged in. --->
<cfif NOT SESSION.LoggedIn>
<!--- ... do something ... --->
Would it be a good idea to include a
there to make sure the structure gets cleared?