+3

Calling OnSessionEnd from outside the Application.cfc to log out users

ColdFusion

On many applications that I run across that have some type of user management, I often see users being logged out by just using StructClear(session).

Although it is a bit of a strong arm approach, it is effective and somewhat appropriate in many applications. But what if you have some type of clean up that you require when a user logs out either by choice in the way of hitting "logout" or by session timeout? One real life example could be on something like an Insurance Application form. If a user has gotten called away from their computer after entering 5 or 6 pages worth of personal and medical information and their session times out, what are the chances that they will actually put themselves through that process again? And if so, what are the chances they would come to the same site!? Likewise, they might want to logout purposefully and come back later. In either case, you would want to preserve what they have so that they happily return to see their data that they had spent entering on previously.

Another case might be an application scoped structure that keeps track of what users are currently logged into the system and how long they have been on. As the users drop off, you would want to maintain this structure and remove the exiting parties.

OnSessionEnd() is a great way to manage this for sessions that naturally time out. Simply put the necessary clean up routines in there, and let it do its thing. But what do you do on your logout process? If you call StructClear(session), the OnSessionEnd() never has an opportunity to run. You could of course duplicate your code.... but then you know we would all be talking about you behind your back!

Fortunately the answer to this is quite simple and easy. You only need to invoke the OnSessionEnd() method from where ever your logout routine is where you might have previously placed a StructClear(session).

The code...

Here is a small code example to test out the idea of the application scoped UserStruct.

Let's start with a very simple Application.cfc. We are going to only include the OnRequestStart() and OnSessionEnd() methods.

Application.cfc

<cfcomponent>	
	<cfscript>
       	this.name = "OnSessionEndTest";
       	this.applicationTimeout = CreateTimeSpan(1,0,0,0);
       	this.sessionmanagement = true;
       	this.sessiontimeout = CreateTimeSpan(0,0,1,0);
   </cfscript>
     
   <cffunction name="OnRequestStart">
       <cfargument name="requestname" required="true" />
 		<cfif StructKeyExists(application,"ApplicationName")>
			<!--- initialize the UserStruct into the application scope --->
			<cfif NOT StructKeyExists(application,"UserStruct")>
				<cflock name="#application.ApplicationName#_UserStruct" type="exclusive" timeout="30">
					<cfif NOT StructKeyExists(application,"UserStruct")>
						<cfset application.UserStruct = StructNew() />
					</cfif>
				</cflock>	
			</cfif>
		</cfif>
	</cffunction>
	
   <cffunction name="OnSessionEnd" output="false">
       	<cfargument name="sessionScope" required=true />
       	<cfargument name="applicationScope" required=true />
		<cfif StructKeyExists(arguments.sessionScope,"UserId") >
 			<cftry> 
				<cfset StructDelete(arguments.applicationScope.UserStruct,arguments.sessionScope.UserId) />
 				<cfcatch/>
			</cftry>
		</cfif>
   </cffunction>
</cfcomponent>

There are a few notable thing in that file:

 

  • this.sessiontimeout - You will notice we have set this to 1 minute. When we let our user timeout in a moment, we aren't going to have to wait too long.
  • OnRequestStart() - Here we are going to build the application.UserStruct if it doesn't already exist.
  • OnSessionEnd() - In here you will see that we are going to attempt to remove session.UserId from the application.UserStruct whenever a session times out.

Now we can create a few small supporting files.

  • DumpLoggedInUsers.cfm - this file will dump all logged in users from the application.UserStruct. Below that you will see a dump of the session scope so you can see the actual session.UserId value
    <cfdump var="#application.UserStruct#" />
    <cfdump var="#session#" />
  • LogInUser.cfm - this file will "log in" a user with UserId 77 and stick them into the application.UserStruct
    <cfset session.UserId = 77 />
    <cfset application.UserStruct[77] = Now() />
  • BadLogoutUser.cfm - this logout method will only do a StructClear(), effectively loggin out the user, but leaving the UserId in the application.UserStruct
    <cfset StructClear(session) />
  • LogOutUser.cfm - this file will emulate an active logout such as someone clicking a "logout" link. Notice how we use the command to call the OnSessionEnd() method of the Application.cfc
    <cfinvoke component="Application" method="onSessionEnd" sessionScope="#session#" applicationScope="#application#" />


Now to run it...

First run the file /DumpLoggedInUsers.cfm. You will see an empty application.UserStruct and you will notice that there is no value for session.UserId.

Now we can go spoof a login by running /LoginUser.cfm.

When you go back to /DumpLoggedInUsers.cfm, you will now see a struct key "77" with the value of #Now()# from whenever you hit the login page. In addition you will see session.UserId is 77 in the session dump.

Next let's look at how ineffective the /BadLogoutUser.cfm is. Go run that and revisit the /DumpLoggedInUser.cfm. You will now see the UserId is gone from our session, but it still exists in our application.UserStruct. This tells us that even though we killed the session, the OnSessionEnd() method was never called.

Now, go back and "log in" by going to /LoginUser.cfm. Check out /DumpLoggedInUsers.cfm again to make sure you have a session.

This time, we are going to logout with /LogOutUser.cfm. When you look at our dump this time you can see that not only did we end the user's session, but we ran the clean up routines in the OnSessionEnd() method of the Application.cfm.

 

Michael Sharman said:
 
Hi Dave,

Not sure I understand why you are putting application initialisation code in onRequestStart(), wouldn't you put this in onApplicationStart()? Then it would always be there for each page request
 
posted 321 days ago
View Replies (1) || Add Comment Reply to: this comment OR this thread
 
.: HIDE REPLIES :.
 
In theory, you are absolutely right. However, I have had some applications quite some time back where things I had set in OnApplicationStart() had magically disappeared. Since that time I have always been in the habit of ensuring it is there on each request. It is a small hit just to simply check if the variable exists.
 
posted 321 days ago
Add Comment Reply to: this comment OR this thread
 

Search