Suppose you want to get information about a user from a database at login, load that information into memory for the session, and then retrieve that information for that specific session from memory at the begining of each page. The pages can use form, URL, or cookie variables to provide the session identifier so ColdFusion will know which session this is. The pages can also pass the UserID again so that your application can compare it with the UserID you stored in session memory to prevent user A from pretending to be user B.
To make this combination of session ID and UserID resistant to tampering by the user, you'd like to combine it with some random information and encrypt the result. However, if you don't use clear-text URL or cookie variables with the exact names that ColdFusion expects its default session mechanism to use, how do you get ColdFusion to access the right session in memory? That's the subject of this demonstration.
Let's review the problem once more before approaching a solution. When you first browse a domain, ColdFusion assigns a session if session management is enabled on the page. When you browse another page in the domain, ColdFusion looks for variables named CFID and CFTOKEN in a URL or cookie. If it doesn't find them or can't match them to an active session in memory, it assigns a NEW session when session management is enabled for that subsequent page. Let's say that again. You have to enable session management to use session memory. If ColdFusion hasn't already recognized the session before session management is enabled, it generates a NEW session with a different session ID and a different structure in memory. If you try to use code to set session variables such as the session ID and token before session management is enabled so you can get the right structure in memory, ColdFusion will (naturally) complain that you can't set session IDs if session management isn't enabled. After session management has been enabled, ColdFusion ignores your attempts to set the session ID and token; because, it has already created a new session. So, how can you win?
The overall flow goes something like this. On the first page, enable session management (so you can use session memory) with the cookie setting turned off (so ColdFusion won't write cookies unless you tell it to), store the session.cfid and session.cftoken along with the UserID in an encrypted string in the cookie. On subsequent pages, decrypt the string, set url.cfid and url cftoken, and then enable session management for the page (again, with the cookie setting turned off). ColdFusion will read the "URL" variables to select the right session in session in memory, and you can compare the UserID in memory with the UserID in the cookie to decide if it's safe to use the other information stored for this session.
<!--- Remove old cookies and set session parameters ---> <cfcookie name="cfid" value=0 expires=-1> <cfcookie name="cftoken" value=0 expires=-1> <cfcookie name="myid" value=0 expires=-1> <cfcookie name="mytoken" value=0 expires=-1> <cfapplication Name="HideSession" clientmanagement="no" sessionmanagement="yes" setclientcookies="no" sessiontimeout=#createTimeSpan(0,0,3,0)#>
For this demonstration, replace database logic by simply pretending that the "Frank" login was validated and that the database says Frank has AccessLevel 5. Then concatenate the Application name and session ID to form a lock name that should only block another window of this same browser, copy cftoken to a variable that doesn't have to be locked, and store the UserId and AccessLevel in a memory structure for this session.
<!--- Pretend that "Frank" login was validated and that the "database" said Frank has AccessLevel 5 ---> <cfset form.UserId="Frank"> <cfset request.AccessLevel=5> <!--- Copy session identifiers and store data in session memory ---> <cfset Lock="HideSession"session.cfid#> <cflock name="#Lock#" timeout=30> <cfset request.cftoken=session.cftoken> <cfset session.User=form.UserId> <cfset session.Access=request.AccessLevel> </cflock>
Use list functions to build a list of elements separated by spaces, urlEncode it to make its special characters safe, and encrypt the result as you store it in a cookie named "Pass". It's a bad idea to permit spaces in UserIDs anyway; this avoids problems in case someone wants to have a comma in a UserId [groan]. The random number just adds aggravation. It doesn't do much in this demonstration, but if you rearranged the pieces before (or after) encryption, it might make the string appear even more different from attempt to attempt.
<!--- Store the UserID and session keys in an encrypted cookie ---> <cfset Temp=""> <cfset Temp=listAppend(Temp,randRange(0,9999)," ")> <cfset Temp=listAppend(Temp,session.cfid," ")> <cfset Temp=listAppend(Temp,request.cftoken," ")> <cfset Temp=listAppend(Temp,form.UserId," ")> <cfset cookie.Pass=urlEncodedFormat(encrypt(Temp,"welcome"))>
Because this is a demonstration, you need a method to help you realize what the session Id was on the first page so you can see if the session retained its integrity between pages. Build a form to help with this task.
<form name="Gate" action="menu.cfm" method="post"> Notice the session Id: <cfoutput>#session.cfid#</cfoutput><br> <input type="hidden" name="Reference" value=<cfoutput>"#session.cfid#"</cfoutput>> <input type="submit" name="Go" value="Done reading"> </form>
<!--- Read the Pass ---> <cfparam name="cookie.Pass" default=""> <cfif not len(cookie.Pass)> The credential is missing </cfif> <pre> urlEncoded Pass:<cfoutput>#cookie.Pass#</cfoutput><br> urlDecoded Pass:<cfoutput>#urlDecode(cookie.Pass)#</cfoutput> </pre> <p> <!--- Interpret the Pass ---> <cfset Temp=decrypt(urlDecode(cookie.Pass),"welcome")> <cfoutput>Random number is #listGetAt(Temp,1," ")#</cfoutput> <p> <cfset url.cfid=listGetAt(Temp,2," ")> <cfset url.cftoken=listGetAt(Temp,3," ")> <cfset url.UserId=listGetAt(Temp,4," ")>
Set session parameters as before, and read session memory. If for some reason the session does not contain a UserId and SessionLevel - perhaps the old session didn't restart after all - use cf try-catch combinations to avoid exposing the user to raw errors. Finally, display the desired and actual data.
<!--- Set session parameters ---> <cfapplication Name="HideSession" clientmanagement="no" sessionmanagement="yes" setclientcookies="no" sessiontimeout=#createTimeSpan(0,0,3,0)#> <!--- Access session memory to prove the session was found ---> <cfset Lock="HideSession"session.cfid#> <cflock name=Lock timeout=30> <cftry> <cfset request.UserId=session.User> <cfcatch> UserId not found<br> <cfset request.UserId=""> </cfcatch> </cftry> <cftry> <cfset request.AccessLevel=session.Access> <cfcatch> SessionLevel not found<br> <cfset request.AccessLevel=0> </cfcatch> </cftry> </cflock> UserId desired: Frank<br> UserId seen: <cfoutput>#request.UserId#</cfoutput><br> Access Level desired: 5 <br> AccessLevel seen: <cfoutput>#request.AccessLevel#</cfoutput><br> Session desired: <cfoutput>#form.reference#</cfoutput><br> Session seen: <cfoutput>#session.cfid#</cfoutput>
So what did you accomplish? You used encryption and an additional identification element to reinforce ColdFusion's already intuitive session management. =Marty=