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

Use preserveCase Consistently When Setting And Expiring Cookies In ColdFusion

By
Published Comments (4)

Yesterday, I demonstrated that using structDelete() with the cookie scope doesn't actually delete the key from the cookie scope in ColdFusion. This was just one of a number of little hurdles that I recently came across when dealing with cookies. Another feature that I tripped over was the use of preserveCase, which tells ColdFusion to use the explicitly provided key-casing for the cookie name. This feature works. But, I didn't realize (at first) that I needed to use it consistently for both setting and expiring a cookie.

To see what I mean, let's look at a rather trite example. In the following code, we're going to toggle the existence of a cookie by setting it and then expiring in on alternating requests to the server. What you should notice is that I'm using preserveCase when creating the cookie, but omitting it when I am deleting the cookie:

<cfscript>

	// We are toggling a cookie on and off on each request. If this works, you
	// should see it submitted in the REQUEST HEADERS of one request but then
	// not in the request headers of the next request.

	if ( structKeyExists( cookie, "theTick" ) ) {

		writeOutput( "Cookie exists, expiring it." );

		// NOTE: Behind the scenes, expiring a cookie sends Set-Cookie response headers.
		cfcookie(
			name = "theTick",
			expires = "now"
		);

	} else {

		writeOutput( "Cookie missing, creating it." );

		cfcookie(
			name = "theTick",
			value = "Sanity, you're a madman!",
			expires = "never",
			preserveCase = true
		);

	}

</cfscript>

If I clear my cookies and then run this page a few times, here's the (concatenated) page output that I get:

Cookie missing, creating it.
Cookie exists, expiring it.
Cookie exists, expiring it.
Cookie exists, expiring it.

As you can see, the cookie is not toggling on and off because we are not actually deleting it. At first, it may not be obvious why this is happening. What you have to understand is that when you delete or expire a cookie, what you're actually doing is sending a "Set-Cookie" header in the response. And, this is why you have to use preserveCase consistently. In this demo, if you examine the "Set-Cookie" header for the first request, we get:

Set-Cookie:theTick=Sanity%2C%20you%27re%20a%20madman%21; Expires=Sun, 03-Dec-2045 11:20:31 GMT; Path=/

Notice the key-casing of the cookie name is "theTicket." Now, let's look at the "Set-Cookie" header of the subsequent "delete" requests:

Set-Cookie:THETICK=; Max-Age=0; Path=/

Here, you an see that the key-casing is "THETICK". Cookie names are case-sensitive (at least in the browser). Which means that "theTick" and "THETICK" are actually two different cookies. Which is why we are never able to toggle the cookie using the above code.

To fix this, we need to add the preserveCase flag to the expires action as well:

<cfscript>

	// We are toggling a cookie on and off on each request. If this works, you
	// should see it submitted in the REQUEST HEADERS of one request but then
	// not in the request headers of the next request.

	if ( structKeyExists( cookie, "theTick" ) ) {

		writeOutput( "Cookie exists, expiring it." );

		// NOTE: Behind the scenes, expiring a cookie sends Set-Cookie response headers.
		cfcookie(
			name = "theTick",
			expires = "now",
			preserveCase = true // NOTE: Using even for expires action.
		);

	} else {

		writeOutput( "Cookie missing, creating it." );

		cfcookie(
			name = "theTick",
			value = "Sanity, you're a madman!",
			expires = "never",
			preserveCase = true
		);

	}

</cfscript>

As you can see, I'm consistently passing preserveCase to all CFCookie actions. And, this time, when we clear cookies and refresh the page, we get the following (concatenated) page output:

Cookie missing, creating it.
Cookie exists, expiring it.
Cookie missing, creating it.
Cookie exists, expiring it.

As you can see, the cookie is being set and then successfully deleted on alternating requests. And, if we look at the "Set-Cookie" header for the expires action, we can see that it matches the original set action:

Set-Cookie:theTick=; Max-Age=0; Path=/

Now that we know that we have to use preserveCase to delete a cookie (that was set using preserveCase), I wanted to see how this affects the use of structDelete() as a means to expire a cookie. As I discussed in my previous post, calling structDelete() on the Cookie scope sends a "Set-Cookie" header behind the scenes. So, let's see how this works when we are use preserveCase with the setting of the cookie:

<cfscript>

	// We are toggling a cookie on and off on each request. If this works, you
	// should see it submitted in the REQUEST HEADERS of one request but then
	// not in the request headers of the next request.

	if ( structKeyExists( cookie, "theTick" ) ) {

		writeOutput( "Cookie exists, expiring it." );

		// CAUTION: When deleting the cookie via structDelete(), we do not have
		// the opportunity to enable preserveCase. As such, it may not actually
		// be able to delete the cookie, depending on how the cookie was created.
		// --
		// NOTE: Behind the scenes, structDelete() on the cookie scope will send
		// Set-Cookie response headers.
		structDelete( cookie, "theTick" );

	} else {

		writeOutput( "Cookie missing, creating it." );

		cfcookie(
			name = "theTick",
			value = "Sanity, you're a madman!",
			expires = "never",
			preserveCase = true
		);

	}

</cfscript>

This time, when I flush the cookies and refresh the page a few times, I get the following (concatenated) output:

Cookie missing, creating it.
Cookie exists, expiring it.
Cookie exists, expiring it.
Cookie exists, expiring it.

As you can see, we a failing to toggle the cookie because the delete / expires action is failing. And if we look at the "Set-Cookie" header of the expires request we can see that key-casing has not been preserved:

Set-Cookie:THETICK=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/

As an aside, it's interesting to see that, in ColdFusion 11, when using structDelete() the "Set-Cookie" header is using "Expires" where as the CFCookie-based expiration uses "Max-Age". This contrasts with ColdFusion 10, which uses "Expires" for all expiration actions, whether you're using CFCookie or structDelete(). I wonder why the change in ColdFusion 11?

So, we see that we may not be able to use structDelete() in conjunction with a cookie set using preserveCase (depending on the casing used in the original key). But, ColdFusion 11 added a new per-application setting - preserveCaseForStructKey - to help preserve the casing of struct keys even if they weren't set using bracket-notation. As I final experiment, I wanted to see how this per-application setting applies to the cookie life-cycle.

First, let's set up our Application.cfc to maintain key-casing:

component
	output = false
	hint = "I define the application settings and event handlers."
	{

	// I am the application settings.
	this.name = hash( getCurrentTemplatePath() );
	this.applicationTimeout = createTimeSpan( 0, 0, 10, 0 );

	// Enable key-case preservation to see how this affects Cookie names. While
	// we are not serializing anything in this demo, this setting affects more
	// than just serialization.
	this.serialization = {
		preserveCaseForStructKey: true
	};

}

Now, let's try to toggle the cookies using nothing but Cookie struct access:

<cfscript>

	// We are toggling a cookie on and off on each request. If this works, you
	// should see it submitted in the REQUEST HEADERS of one request but then
	// not in the request headers of the next request.

	if ( structKeyExists( cookie, "theTick" ) ) {

		writeOutput( "[ App.cfc Version ] Cookie exists, expiring it." );

		// NOTE: Behind the scenes, structDelete() on the cookie scope will send
		// Set-Cookie response headers.
		structDelete( cookie, "theTick" );

	} else {

		writeOutput( "[ App.cfc Version ] Cookie missing, creating it." );

		// By setting a key on the cookie scope, we're creating a "session cookie".
		cookie.theTick = "Sanity, you're a madman!";

	}

</cfscript>

By writing directly to the Cookie struct, we lose the ability to set any of the cookie-based attributes. Ultimately, this ends up creating a "session cookie" that is expunged when the browser is closed. When we go to run this application, here's the "Set-Cookie" header that we get:

Set-Cookie:theTick=Sanity%2C%20you%27re%20a%20madman%21; Path=/

As you can see, the key-casing of the cookie name was preserved as "theTick." However, when we refresh the page and issue the expiration, we get the following "Set-Cookie" header:

Set-Cookie:THETICK=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/

Unfortunately, you can see that, even when preserveCaseForStructKey is enabled, deleting a cookie using structDelete() fails to use the appropriate cookie name. Which fails to delete the cookie. Which means we get the following (concatenated) output when we refresh the page a number of times:

[ App.cfc Version ] Cookie missing, creating it.
[ App.cfc Version ] Cookie exists, expiring it.
[ App.cfc Version ] Cookie exists, expiring it.
[ App.cfc Version ] Cookie exists, expiring it.

In ColdFusion, there's a number of ways to both create and expire / delete cookies. But, based on these findings, it seems you either have to use preserveCase never or always. Trying to mix and match preserveCase (including the preserveCaseForStructKey per-application setting) is going to get you in trouble, when it comes to Cookies in ColdFusion. Definitely a minor note but, something to be aware of.

Want to use code from this post? Check out the license.

Reader Comments

84 Comments

I discovered a recent issue regarding unencoded unicode cookie values. A JWPlayer language drop-down for sub-titles was causing problems on a ColdFusion website. The JWPlayer labels for the languages are used to create cookies and the unencoded unicode "Español" cookie value caused subsequent web requests to ColdFusion 10 & 11 to be "empty".

Unicode characters are allowed if properly encoded. If not encoded, any web requests to ColdFusion 10 & 11 will return an immediate "500 Server Error". (I believe that I reported this bug prior to the release of ColdFusion 11u7 & 10u18.)
http://stackoverflow.com/a/33292348/693068
https://gist.github.com/JamoCA/f8586d0dafc462cfd5d1

The only way I've found to currently work around this ColdFusion bug is to either 1) manually delete the offending cookie using dev tools or 2) write a URL Rewrite rule to catch the bad request and/or use javascript to delete the cookie cookie.

15,848 Comments

@James,

Very interesting. I did see that ColdFusion 10 added the "encodevalue" option for the CFCookie tag; but, the explanation of what it does it non-valuable. From what you're saying, it sounds like it encodes Unicode characters.

448 Comments

Legend. This has saved me so much time Ben.
Especially when using javascript to delete browser cookies [httpOnly=false].

Just out of interest, if I set then CF "httpOnly" cookie attribute to true, does Coldfusion remove all the browser cookies after moving them into the header?

Or am I responsible for removing them from the browser?

Thanks

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