One of the great features of ColdFusion that makes programming so easy is the way in which ColdFusion seamlessly maintains user Sessions across multiple page requests. It can do this by automatically setting and parsing session cookies; or, it can grab user-supplied session tokens from the URL. In either case - cookies or URL parameters - ColdFusion takes the supplied session tokens and automatically associates the correct session memory space with the current page request.
Having to maintain session without cookies is certainly a pain. But, that said, as long as the API / SMS text message requests to our ColdFusion application are sufficiently predicable, we can still leverage the implicit session management that the ColdFusion framework provides. The trick to this is in fully understanding when the current request is associated with the appropriate Application and Session memory spaces.
When you make a request to your ColdFusion application, the ColdFusion server creates and utilizes an instance of your Application.cfc component. As this happens, the following series of events takes place (as far as I can theorize based on personal observations and guesstimations):
The ColdFusion application server initializes the common scopes - FORM, URL, COOKIE, etc.
The ColdFusion server creates an Application.cfc instance and the Application.cfc pseudo constructor executes (this is the code in your Application.cfc that resides outside of any CFFunction tags).
The ColdFusion server examines the initialized Application.cfc data to see what Application memory space to access (based on the this.name property).
The ColdFusion server examines additional information - cookies and URL variables - to see what session memory space to access.
The ColdFusion server triggers the appropriate application event handlers (ex. OnApplicationStart()) defined in the Application.cfc.
The most critical fact to take note of in the time line above is that the Application and Session memory spaces are associated to the current page request after the pseudo constructor has executed. We can traditionally leverage this fact to define request-specific session properties such as dynamic management and session timeouts. But, even cooler than that, we can use the pseudo constructor space to create custom session tokens before the current request is even associated with a session!
Previously, I stated that our requests needed to be sufficiently predictable before we could use this technique. By that, I mean that each unique user does need to pass something unique (but consistent) to your application. Perhaps that's an API key or an IP address. When dealing with the TextMarks SMS web proxy, we can configure our URLs to pass through the ID of the cell phone. This is a value that TextMarks assures us is unique to each phone and consistent across each request. The following is the URL I have configured:
As you can see, the "\u" requires TextMarks to pass through the unique phone ID with each proxied SMS text message request.
Once we have this unique value, we can use it to forge our own session tokens. All we have to do is create a CFID and CFTOKEN value and put them in a place where ColdFusion will look for them when trying to associate the current page request to the appropriate session memory space. Since ColdFusion session management works most naturally with cookies, I chose to put them in the COOKIE scope. In the following Application.cfc, I use the pseudo constructor and the url-based phone ID to create usable session tokens:
<cfcomponent output="false" hint="I define the application settings and event handlers."> <!--- Define the application. ---> <cfset this.name = hash( getCurrentTemplatePath() ) /> <cfset this.applicationTimeout = createTimeSpan( 0, 1, 0, 0 ) /> <cfset this.sessionManagement = true /> <!--- Since this app is designed for API usage, I'm going to leave the session timeout very small until we confirm that the given request is being made by a valid API user. ---> <cfset this.sessionTimeout = createTimeSpan( 0, 0, 1, 0 ) /> <!--- ------------------------------------------------- ---> <!--- BEGIN: API-Session Hack ------------------------- ---> <!--- ------------------------------------------------- ---> <!--- This is the unique ID of the cell phone making the request. This ID is supplied by TextMarks and is garanteed to be a unique numeric value. CAUTION: Normally, we would not put this in the pseudo constructor of our Application.cfc; however, since the cell phone cannot maintain session across requests implicitly, we are going to hack the session by creating is based on the UID that is sent by TextMarks. While we are faking the CFID/CFTOKEN, because we are setting it in the cookie, then ColdFusion should create a session and associate it with our faked IDs. NOTE: Because UID is unique, we don't have to worry about our session clashing. ---> <cfparam name="url.uid" type="numeric" default="0" /> <!--- Check to see if we have a UID with which to make a fake session. ---> <cfif url.uid> <!--- Use the UID to create fake CFID and CFTOKEN values. While we might worry about session clashing (with people who are not using phones), since we CFID is usually 4 digits, as long as we create a CFID that is greater than 4 digits AND uses the UID, then we should not have any collisions. ---> <cfset cookie.cfid = ( repeatString( "1", max( 0, (5 - len( url.uid )) ) ) & url.uid ) /> <!--- Since the CFID is already going to prevent collisions, we can use the UID as the CFTOKEN as well. ---> <cfset cookie.cftoken = url.uid /> <!--- Because we are validating this user as an actual cell phone user, let's increase the session timeout over the default (which is left very small). ---> <cfset this.sessionTimeout = createTimeSpan( 0, 0, 30, 0 ) /> </cfif> <!--- ------------------------------------------------- ---> <!--- END: API-Session Hack --------------------------- ---> <!--- ------------------------------------------------- ---> <!--- Define page request settings. ---> <cfsetting showdebugoutput="false" /> <cffunction name="onApplicationStart" access="public" returntype="boolean" output="false" hint="I initialize the application."> <!--- Return true so the request is processed. ---> <cfreturn true /> </cffunction> <cffunction name="onSessionStart" access="public" returntype="void" output="false" hint="I initialize the session."> <!--- Set the default hit count. This is how we will track to see if the session is kept across SMS requests. ---> <cfset session.hitCount = 0 /> <!--- Return out. ---> <cfreturn /> </cffunction> <cffunction name="onRequest" access="public" returntype="void" output="true" hint="I execute a rendering template."> <!--- No matter what page was requested, just include our front controller. ---> <cfinclude template="./index.cfm" /> <!--- Return out. ---> <cfreturn /> </cffunction> </cfcomponent>
As you can see above, I take the url parameter, UID, and use it to create user-specific CFID and CFTOKEN values. Not only will ColdFusion use these customized CFID and CFTOKEN values to associate the request with the correct session memory space (effectively enabling session management), because ColdFusion will use the same tokens across session timeouts, this works just as well with the first request as it does for all subsequent requests.
While this application is meant to be used in the context of cellular phones only (and TextMarks specifically), I have added some additional logic around the CFID creation to help prevent session collisions. Since the standard CFID value is only 4 digits when created by a cookie-based user, I know that by creating a 5+ digit CFID value for SMS-based requests, the phone-based sessions will never collide with the standard, web-based sessions.
To test this out, I created a very simple front-controller that counts the number of requests made by a given cell phone user and then returns that count in the TextMarks response:
<!--- Increment the hit count for this session. NOTE: Whlie the SMS request should not be able to hook back into the existing session implicitly (as it does not pass cookies along with the request), our Application.cfc hack has made that possible. As such, our application can operate under the assumption that session management is intact. ---> <cfset session.hitCount++ /> <!--- Build the response. ---> <cfsavecontent variable="responseData"> <cfoutput> <!--- Check to see if this is the first hit or a subsequent hit (just so we can customize the response slightly). ---> <cfif (session.hitCount eq 1)> Hello!<br /> Thanks for visiting.<br /> <cfelse> Welcome Back!<br /> Visit: <cfoutput>#session.hitCount#</cfoutput><br /> </cfif> </cfoutput> </cfsavecontent> <!--- At this point, we should have a valid response in our respondData variable. Now, we need to clean it up for SMS response. Let strip out the extra white space, including the leading / trailing line spaces. NOTE: TextMarks will automatically take care of stripping out our <BR /> tags and replacing them with text-valid line breaks. ---> <cfset responseData = reReplace( trim( responseData ), "(?m)(^[ \t]+|[ \t]+$)", "", "all" ) /> <!--- Convert the respond data into a binary variable so that we can easily stream it back using CFContent without having to be fanatical about clearing the content buffer. ---> <cfset binaryResponse = toBinary( toBase64( responseData ) ) /> <!--- Set headers. ---> <cfheader name="content-length" value="#arrayLen( binaryResponse )#" /> <!--- Stream content back as HTML. By using the Variable attribute, we are ensuring that no extra white space is being passed back. ---> <cfcontent type="text/html" variable="#binaryResponse#" />
As you can see, this front-controller operates as if the implicit ColdFusion session management is intact; and, it can do so because the ColdFusion session management is working as expected. The front controller increments a session-based "hit count" variable that was initialize in the OnSessionStart() event handler, and then creates and returns the response HTML.
When you use a technique like this, of course, there are going to be some security concerns. After all, we are taking several numbers that should be randomly generated by ColdFusion and reducing them down to one, url-based value. This would make brute-force guessing of someone else's session token somewhat easier. That said, all the good security practices that you put in place with a standard ColdFusion application can still be applied here; and, all the precautions you might normally take to ensure someone isn't brute-force guessing session tokens are all still valid.
The ColdFusion application framework is extremely robust and hugely flexible; this gives us as programmers a tremendous amount of power. The trick to harnessing that power, however, is to really understand how the ColdFusion framework works and how page requests within it are processed. Once you understand that, there's a whole world of functionality waiting to be hooked into.
Want to use code from this post? Check out the license.