Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies

Posted September 10, 2009 at 8:14 PM

Tags: ColdFusion

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.

When your application has access to cookies or URL-based CFID and CFTOKEN values, this works perfectly; sometimes, however, neither of these options are available and maintaining the ColdFusion session across multiple page requests gets a bit more complicated. In my previous posts about using TextMarks to SMS-enable our web applications, I had to explicitly cache pseudo-sessions in the Application scope because neither the SMS protocol nor TextMarks' web proxy service allow for the use of cookies or round-trip URL parameters.

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):

  1. The ColdFusion application server initializes the common scopes - FORM, URL, COOKIE, etc.
  2. 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).
  3. The ColdFusion server examines the initialized Application.cfc data to see what Application memory space to access (based on the this.name property).
  4. The ColdFusion server examines additional information - cookies and URL variables - to see what session memory space to access.
  5. 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:

http://www.bennadel.com/resources/demo/textmarks/session/ind ex.cfm?uid=\u

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:

Application.cfc

 Launch code in new window » Download code as text file »

  • <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:

Index.cfm

 Launch code in new window » Download code as text file »

  • <!---
  • 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.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page




Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Sep 11, 2009 at 12:28 PM // reply »
10 Comments

Wow. You're really taking this to the next level. Great work!


Sep 12, 2009 at 9:28 PM // reply »
6,516 Comments

@Aaron,

Thanks man. I thought this was a very interesting post, SMS or not. The ColdFusion application framework let's us do some sneaky stuff!


Nov 20, 2009 at 11:22 AM // reply »
9 Comments

Great hack. This doesn't seem to work on Railo though. The session doesn't stick across requests. Anyone have any suggestions?


Nov 20, 2009 at 1:01 PM // reply »
6,516 Comments

@Dave,

Hmm, that's not good. This seems like a core piece of the ColdFusion application framework. I'm surprised this doesn't work.


Nov 20, 2009 at 1:28 PM // reply »
9 Comments

Yeah, works fine when running the above in a browser (hitting index.cfm?id=12345), but fails to maintain the session when I do a cfhttp get to the page.


Nov 20, 2009 at 2:02 PM // reply »
6,516 Comments

@Dave,

Sounds like a bug in Railo.


Nov 20, 2009 at 3:44 PM // reply »
177 Comments

@Ben / Dave: It's not a bug. CFHTTP does not pass cookies back to the request to persist the session. You have to use cfhttpparam to do so. In Ben's code above, he's using ?id={MADE UP CFTOKEN VALUE} above and the phone browser is passing that value around, Ben is not using cfhttp.


Nov 20, 2009 at 3:58 PM // reply »
6,516 Comments

@Todd, @Dave,

Perhaps I misunderstood what Dave was saying; I thought he meant he was hitting it via CFHTTP passing in the made up token (and session was not being held across CFHTTP requests)?


Nov 20, 2009 at 4:00 PM // reply »
9 Comments

No, Ben has accomplished exactly what I am attempting to do on Railo (and have successfully done on CF8). Ben's example uses textmarks which I'll assume posts incoming sms messages to a CF url Ben specified. Attached to that url is a unique ID (probably phone number). As I understand it, Ben is using that unique ID to specifically set the CFID and CFTOKEN cookies on each request eliminating the need to pass url params around. So the above example works fine on CF8 and not on Railo. Thanks for the help Todd!


Nov 20, 2009 at 4:02 PM // reply »
9 Comments

@ben yes, that's exactly what I am saying


Nov 20, 2009 at 4:05 PM // reply »
177 Comments

@Ben: Currently waiting to see some code from Dave. For all I know, you probably can't generate your own CFID/CFTOKEN in Railo. I won't know until I see / test. In my testing, if I just made up numbers Railo didn't like it and made new ones for me. If I took the CFID/CFTOKEN that Railo created and gave them back to Railo, it was happy. Perhaps it's a security feature.


Nov 20, 2009 at 4:12 PM // reply »
9 Comments

@Todd: Yes, I think that's exactly what's happening. Using Ben's example above I log the value of cookie.cfid right after I set it and it is inded the value I set. In index.cfm I log cookie.cfid and it is indeed different.


Nov 20, 2009 at 4:14 PM // reply »
6,516 Comments

@Todd,

Yeah, sounds like a bug (or simply a difference) in the way Railo handles session management.


Nov 20, 2009 at 4:14 PM // reply »
177 Comments

@Dave: Well, Ben did say in his video that it was a bit of a hack. :D


Nov 20, 2009 at 4:16 PM // reply »
6,516 Comments

@Todd,

Touche ;)


Nov 20, 2009 at 4:18 PM // reply »
177 Comments

Not sure how it's a bug, definitely a difference. I don't want my users or coders creating their own CFID/CFTOKEN. That's the server's job.


Nov 20, 2009 at 4:21 PM // reply »
6,516 Comments

@Todd,

I mean "bug" only in the sense that it deviates from the way "Adobe ColdFusion" implements it.... only so far in that Railo is an open-source version of ColdFusion.

I guess it depends on what you feel is "core" ColdFusion functionality.


Nov 20, 2009 at 4:23 PM // reply »
177 Comments

Is there no unique (device or otherwise) id that gets passed in the request that you can shove into the application scope?


Nov 20, 2009 at 4:31 PM // reply »
6,516 Comments

@Todd,

Yeah, each phone has a unique ID; this is the value that is being used to hack the custom CFID / CFTOKEN session values.


Nov 20, 2009 at 4:42 PM // reply »
177 Comments

My opinion is that I don't think auto-generating your own CFID/CFTOKEN is recommended. I'll have to wait for Micha to answer what the ramifications are, but he probably didn't allow this in Railo for a reason and I can't say I blame him. There are some "features" that work in ACF that Railo team just flat out refuses to acknowledge and takes a strong stance in the name of security or memory. This may be one of them.

Seems to me that your statement from above is the way to go: "I had to explicitly cache pseudo-sessions in the Application scope because neither the SMS protocol nor TextMarks' web proxy service allow for the use of cookies or round-trip URL parameters."

You'd have to watch out for race conditions which means using your <cflocks> wisely.


Nov 20, 2009 at 4:46 PM // reply »
6,516 Comments

@Todd,

That's understandable. I am not sure if this really leaves any more security holes than the fact that using old cookie-based CFID / CFTOKEN values will create a new session using the old CFID / CFTOKEN values (unless this is also something that Railo has also excluded).

Storing the session data in the application scope is something that I tried in the post before this one. It works... but I just thought there was something so graceful about being able to tap into the implicit session management provided by the CF engine :) Not necessary, just a perceived elegance.


Nov 20, 2009 at 4:52 PM // reply »
177 Comments

Case in point, Ben, you may not be aware of this, but in Railo - OnApplicationStart() & OnSessionStart() act differently than in ACF.

ACF does:
OnApplicationStart (1st hit)
OnSessionStart (1st and each hit thereafter)
OnRequestStart (each hit)
OnRequest (each hit)
OnRequestEnd (each hit)
...

Railo does:
OnRequestStart
OnRequest
OnRequestEnd
...

When Railo encounters the 1st <cfset session.{blah} = {whatever}> or <cfset application.{blah} = {whatever}> it fires off OnSessionStart / OnApplicationStart. I heard talks of Railo making this a setting, but so far... it hasn't been heavily pushed because you can get around this by putting: <cfset session.foo = 1> in your OnRequestStart() and everything works as ACF would.

You are probably thinking, why the hell would Railo deviate like that? Answer: Spiders, Crawlers, Bots. Why burn the application memory usage of your server with these requests? Some of these spiders, crawlers and bots are legitimate (indexing), so blocking them does no good.


Nov 20, 2009 at 4:53 PM // reply »
177 Comments

Ben, you can ramp up the security by turning on J2EE session which gives you a third set of numbers other than CFID/CFTOKEN. There's a reason why ACF put this in place (other than just session replication).


Nov 20, 2009 at 5:07 PM // reply »
6,516 Comments

@Todd,

I have seen tidbits about the way Railo handles session. I can understand that it lazy-loads sessions, but I also think that I might make some things more complicated. For example, often times, I like to track the user's point of entry to a site (what page the landed on, where they came from); but, if session management is delayed beyond the initial page, it seems like you'd have to find a hack to store that data.

I suppose each engine will have its own strengths - let's not let this spiral into a ACF vs. Railo debate :) The real trick is finding ways to most leverage each of the differences.


Nov 20, 2009 at 5:17 PM // reply »
177 Comments

Ben, sorry if I didn't make this clear. You can make it work like that if you want, just put <cfset session.foo = 1> (and <cfset application.foo = 1>) in your OnRequestStart() and it reverts to ACF's way of doing it and I mentioned there's talk of making it a setting so the developer can choose.


Nov 20, 2009 at 5:23 PM // reply »
6,516 Comments

@Todd,

Ahh, gotcha, yeah that makes sense.


Nov 20, 2009 at 5:35 PM // reply »
177 Comments

@Dave: I talked to Gert he suggested:
<cfhttp method="get" url="http://{some cf website}" result="stuff" addtoken="yes" />

Note the addition of cfhttp attribute addtoken. That should persist your session without having to create your own CFID/CFTOKEN. They developed this for a client that had to do the same thing. :)


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 20, 2009 at 11:32 PM
Five Months Without Hungarian Notation And I'm Loving It
I've used headless camel case for years for not only ColdFusion variables, but also SQL tables and fields... pretty much everything involving code. I also subscribe to the "don't abbreviate and clea ... read »
Nov 20, 2009 at 11:00 PM
Five Months Without Hungarian Notation And I'm Loving It
@Marcel, Yeah, I always err on the side of longer but more readable variable names. As for the camel casing of CF methods and the headless camel casing of custom items, I get around this by always ... read »
Nov 20, 2009 at 10:56 PM
Five Months Without Hungarian Notation And I'm Loving It
I use the following and love it: my.namespace.MyComponents.functionMethodsOrUDF() CONSTANT_VALUES_OR_PROPERTIES One thing I always try is to CamelCaseBuiltInColdFusionFunctions() so others can tell ... read »
Nov 20, 2009 at 5:38 PM
Learning ColdFusion 8: CFImage Part I - Reading And Writing Images
Hi Ben, Great article. I've been looking around to see if ColdFusion image engine can programatically create the following "wrap around" effect: http://www.creativepro.com/article/photoshop-s-she ... read »
Nov 20, 2009 at 5:35 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Dave: I talked to Gert he suggested: <cfhttp method="get" url="http://{some cf website}" result="stuff" addtoken="yes" /> Note the addition of cfhttp attribute addtoken. That should persist y ... read »
Nov 20, 2009 at 5:23 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Todd, Ahh, gotcha, yeah that makes sense. ... read »
Nov 20, 2009 at 5:17 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
Ben, sorry if I didn't make this clear. You can make it work like that if you want, just put <cfset session.foo = 1> (and <cfset application.foo = 1>) in your OnRequestStart() and it reve ... read »