ColdFusion MX 7 Login Security (Page 12 of 13)
Handling Session Timeouts (onSessionEnd method)
The code in our logout.cfm template will work well to handle manual user logouts. But what happens when a user gets up from their computer and their session times out later on? How do we ensure their session data is cleared in the same way? The answer is the onSessionEnd method of our Application.cfc. This special method does not execute within the context of the browser. Instead, it is called automatically by ColdFusion when a session times out. When CF executes this method, two parameters are passed to it. The first is a reference to the Session scope and the second is a reference to the Application scope. You can call these arguments whatever you want in your method, but their order is very important, so don't get them confused. The first argument, regardless what you call it will always refer to the Session scope and the second the Application scope. This implies you cannot access the Session or Application scope directly using Session.x or Application.x, which is true. Again, this is because the onSessionEnd method is not run in the context of a request or browser. Since you can't access these scopes directly you also cannot issue a scope-based lock on them. In the case of the Session scope, it doesn't matter, because ColdFusion has an implicit lock on the Session scope inside this method. But since we are accessing (and writing) the Application scope, and since we can't lock it in a scope lock, we must use a named lock around our code. For this reason, code that manages our application-wide session tracking will be encompassed in the same named lock, everywhere in our application. It's amazing how one restriction in code will impact so many other things.
Let's take a look at the code:
71. <cffunction name="onSessionEnd" returntype="void"> 72. <cfargument name="SessionScope" required="true"> 73. <cfargument name="ApplicationScope" required="true"> 74. 75. <cflock name="lck_currentSessions" throwontimeout="Yes" timeout="7" type="EXCLUSIVE"> 80. <cfset sessionPosition = ListFind( ArrayToList(arguments.ApplicationScope.sessionData), arguments.SessionScope.sessionid)> 81. <cfif sessionPosition neq 0> 82. <cfset ArrayDeleteAt(arguments.ApplicationScope.sessionData, sessionPosition)> 83. <cfset arguments.ApplicationScope.currentSessions = arguments.ApplicationScope.currentSessions - 1> 84. </cfif> 85. </cflock> 87. </cffunction>
While this may seem simple enough, there are some things you need to keep in mind. First, this method will be called when a users session expires due to inactivity. You may be surprised to learn this includes those users that manually log out of the application! Yes, even though we clear the users session data and decrement the Application.currentSessions variable in the logout.cfm template, the onSessionEnd method will still run once the session timeout is reached. Because of this, we must be sure to first check the Application.sessionData array and only attempt to delete the users session if it just timed out. If the user logged out earlier, their session was already deleted appropriately (in logout.cfm) so there's no need in deleting it now.
Debugging problems with this method, due to it not running in the context of a browser, can be very burdensome. My suggestion when utilizing onSessionEnd is to start very small with some very simple code. Work your way up in complexity so you can determine when things start to break. It may also be handy for you to use the CFLOG tag to write logs to your server with debugging messages. This saved me some precious time when testing the code for this tutorial. Incidentally, I've provided you with a sample CFLOG tag commented out in the method.PREVIOUS | NEXT