Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jessica Eisner
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jessica Eisner

Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies

By on
Tags:

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

<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

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

Reader Comments

15,674 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!

10 Comments

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

15,674 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.

10 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.

218 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.

15,674 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)?

10 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!

218 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.

10 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.

218 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.

15,674 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.

15,674 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.

218 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.

15,674 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.

218 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.

218 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).

15,674 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.

218 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.

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

15,674 Comments

@Todd,

Do you know what the AddToken attribute is doing? Does this rely on previous HTTP calls to get the cookies? Or is it appending the tokens of the existing calling context?

10 Comments

I tried adding the addtoken attribute to my cfhttp call and it didn't do the trick. I'm also wondering what its supposed to do. I tried

218 Comments

Yeah, I don't think this is what Dave is looking for. From brief testing (and, I do mean brief), it appears that Railo is passing cookies back. So while that is neat that it is working, it's not going to work for your mobile development.

Btw, apparently I was spreading some old information. I previously mentioned above that Railo handles Application/Session differently. After talking to Micha at Railo, this was apparently changed to be more like ACF at the end of 3.1 final release.

2 Comments

I know this is an older post - but just wanted to thank you for this little hack. Not only does it work for SMS - but it's a great solution for the problem I was having passing session variables in my Facebook Ap - I just used the form.fb_sig_user instead of the URL.UID and it worked like a charm! Now I can have one session on all my pages in my facebook ap rather than having the Facebook API create a new session everytime a page is hit.

THANK YOU

TK

15,674 Comments

@Tom,

I don't have much experience with FaceBook, but this sounds awesome. Also, as an update, the CFTOKEN value doesn't have to be an integer. In my recent presentation on the ColdFusion Framework, I found that CFTOKEN could be a string. For example, you can do something like:

cfid = form.fb_sig_user
cftoken = "facebookAPI"

In any case, I'm super excited to hear that this approach has provided value.

6 Comments

Ben, I tried to run a similar where I recreated a session by setting the CFID and CFToken. My test worked on CF8 but not CF9. Maybe some of the problems being reported are related to the different servers.

15,674 Comments

@Steven,

I just tried running similar code (from my ColdFusion Application Framework) on my local CF9 instance. It seems to work OK with creating cookies in the pseudo constructor.

Are you sure you're doing it in the *pseudo* constructor?

15,674 Comments

@Steven,

Yeah, doing this is in the pseudo constructor is critical. This part executes before the application or session gets associated with any memory space on the server; as such, this is the point where we can massage the "input" to the request association.

2 Comments

Although cookies are completely harmless some people may not wish to accept them. However pretty much every application and website requires you to accept cookies or else the content you wish to access and use won't become available. This is the same for Cold Fusion, until now, as thanks to Vlad Flippov, and his associate they have come up with a way of rejecting cookies but still keeping your Cold Fusion sessions active.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel