Making the JSESSIONID Session Token Cookie SECURE and HTTPOnly and settings its PATH

Wow, I have been working on this one for a while. I am almost embarrassed to admit that I have spent upwards of 9 hours learning how cookies work in ColdFusion and the JEE server (including another full hour since I started writing this post). A lot of testing and trying out different things has resulted in a lot of frustration, a little code, and (hopefully) a solution.

I have complained before that when using J2EE session management in ColdFusion, that the session token cookies are difficult to work with. When trying to do things like set a specific PATH, or set the cookie as SECURE or HTTPOnly, it can, many times, result it creating duplicate cookies. Well, I think I have finally come up with a method of setting the JSESSION token's PATH, SECURE, and HTTPOnly attributes without this duplication.

I am going to outline this method here. It is pretty simple, but what I would love is for some of you to try this out and give me your feedback. Let me know if it works as you would hope in different environments. This is the code that goes into your Application.cfc

NOTE: This code has been updated from the original post to correct some errors - May 5th, 2009


<cffunction name="onSessionStart" output="false">
        <!--- Expire the old Cookie --->
        <cfcookie name="jsessionid" expires="now"/>
        
        <!--- Get the HTTP Response Object --->
        <cfset response = getPageContext().getResponse() />
        
        <!--- Set the specifics for the cookie --->
        <cfset path = "/test" />
        <cfset domain = cgi.server_name />
        <cfset secure = "Secure" /> <!--- Use val of "Secure" or leave blank --->
        <cfset HTTPOnly = "HTTPOnly" /> <!--- Use val of "HTTPOnly" or leave blank --->
        
        <cfscript>
            header = "jsessionid" & "=" & session.sessionid & ";domain=." & domain & ";path=" & path & ";" & secure & ";" & HTTPOnly;
            response.addHeader("Set-Cookie", header);
        
</cfscript>
        
    </cffunction>

First, we are expiring the cookie that is being set by the JEE server. We cannot use this.setClientCookies=false here, because that only works for ColdFusion session token cookies (CFID and CFToken).

Next, we get the response object using the getPageContext() function.


    <cfset response = getPageContext().getResponse() />

The next few steps are simply some dynamic creation of attributes for the cookie. These can be hard-coded if you prefer.


    <!--- Set the specifics for the cookie --->
    <cfset path = ListDeleteAt(cgi.script_name, ListLen(cgi.script_name, '/') , "/")>
    <cfset domain = cgi.SERVER_NAME />
    <cfset secure = "Secure" /> <!--- Use val of "Secure" or leave blank --->
    <cfset HTTPOnly = "HTTPOnly" /> <!--- Use val of HTTPOnly or leave blank --->

Finally, the important part. We create a new string for setting the cookie in an HTTP message header. Ultimately, this will result in a string similar to this:


     jsessionid=0a304cd0a304b2549000a30456c5232802;Domain=my.local.com;Path=/test;Secure;HTTPOnly

Of course, you would replace the PATH with the path of your application and the Domain with whatever domain you are working in, or the cookies won't work. Also keep in mind, that if you set the cookie to SECURE=true and you are not on SSL, things will nto work as you expect.

We pass that string into the addHeader() method of the response object to set the new cookie into the response.

This seems to work as I had hoped for setting PATH, SECURE and HTTPOnly, with one small exception. I say it is a small exception, because I don't think it will affect 99.999% of applications. But for some reason, on a Non-SSL connection, when you use this method, it always sets the cookie to HTTPOnly, even if you do not specify it. To me, this is no a big deal, I would still explicitly set it in case that ever gets fixed. UPDATE: This previous paragraph was behavior that resulted from forgetting to comment out code that was setting the cookie a second time. So the cookies work as expected in all cases that I tested.

So, please let me know what you think, try it out. I'd love to get feed back. I tested this with ColdFusion 8, on Apache 2.2 with both a SSL and Non-SSL connection.

Comments
masterorb's Gravatar Jason,

Thank you so much! A thrilling development! Here is a screenshot for you:

http://tinyurl.com/c9vjh7

Using FireCookie it appears that JSESSIONID *is* HTTPOnly. So great. (I am running this stuff against a security assessment program called Hailstorm by Cenzic, and sometimes things that FireCookie says are true come back differently in Hailstorm, but I'll tell you if that happens.)

Differing paths looks great.

The one thing that is strange for me is that I cannot set secure=false to make the cookie comply with non-SSL. (I know that non-SSL is a big no-no anyway but the organization is still in the process, etc. etc.)

I simply alter your code:

<cfset secure = "false" >
instead of
<cfset secure = "true" >

But as you can see in the screenshot FireCookie still says that it's a secure cookie. So since I'm developing this without SSL I get booted out of the site a la jalpino's system he linked in security series 12.1.

I tried hard-coding the secure property as a desperate measure but no dice.

Like I said I know that ultimately doing any of this without SSL is a bad practice, but I also see that you went through the trouble and wanted to report back.

This is on CF 8,0,1,195765 running on Apache.
# Posted By masterorb | 5/6/09 3:50 PM
Jason Dean's Gravatar @masterorb,

I am noticing some similar behavior with the non-ssl connection. I know that sometimes SSL is not an option. Try this header string instead, it seems to fix the Non-SSL HTTPOnly issue for me.

header = "jsessionid" & "=" & session.sessionid & ";Domain=" & domain & ";Path=" & path & ";HTTPOnly";
# Posted By Jason Dean | 5/6/09 8:45 PM
Jason Dean's Gravatar @masterorb,

The problem you are having with the cookie being SECURE regardless of whether you set it as TRUE or FALSE seems to be occuring because, like the HTTPOnly flag, it does not seem to matter if it is TRUE or FALSE, it only matters that it exists. So even if we set secure=false, the browser sees the SECURE flag and makes the cookie secure.

I have updated the onSessionStart() code sample above to correct this, try it out and let me know.

Thanks
# Posted By Jason Dean | 5/6/09 9:30 PM
masterorb's Gravatar Thanks yes works perfectly now.

Only thing is that I was horrified to see this morning when I went to test that my CFID and CFTOKEN cookies had come back, according to Firebug, even though I have setclientcookies="false".

I assume that this is a problem with my code, and yours has nothing to do with it, but I just wanted to report back.
# Posted By masterorb | 5/7/09 5:29 AM
masterorb's Gravatar also, you left a bit of hardcoding in your first code window: <cfset path = "/test" />

thanks again.
# Posted By masterorb | 5/7/09 5:33 AM
Mike's Gravatar This solved my cookie path problem in one shot! But I'm sorry to say I don't think it helps with the secure flag problem...

I did a lot of tests and found that Coldfusion is bit tricky on the session and cookies management with onSessionStart: it sets the session cookie after your code. So I was trying to overwrite the jsessionid cookie using a cfheader with no success (always came before the actual server jsessionid cookie in the http headers). Using cfcookie makes it work: it comes even after the server cookie setup and overwrite it effectively.

Checking the cookies sequence in the http headers (using Live HTTP headers plugin), I couldn't find any difference between your solution (with a cfscript and http response) and a cfheader tag: the manually set jsessionid cookie comes always before the server set and the expiration command.
Using your code, the http trace of the reply contains (in this order):
Set-Cookie: jsessionid=3e3063c377f93d5fc7291325378363d3e7f5;Domain=my.local.com;Path=/test;Secure;HTTPOnly
Set-Cookie: JSESSIONID=3e3063c377f93d5fc7291325378363d3e7f5;path=/
Set-Cookie: JSESSIONID=;expires=Wed, 07-May-2008 11:03:52 GMT;path=/

I think what makes it work, is the domain/path specification and the lower case of "jsessionid" which are discriminative for Firefox. If you specify / as path without a domain and use JSESSIONID as name, your cookie with the secure flag will be overwritten.

I guess putting a secure flag without a domain/path doesn't make any sense so this is a great workaround anyhow!
# Posted By Mike | 5/7/09 5:39 AM
masterorb's Gravatar @Mike

Are you saying that even after putting Jason's code in, you get both jsessionids set by your code and JSESSIONIDs set by CF ?
# Posted By masterorb | 5/7/09 9:38 AM
Jason Dean's Gravatar @Mike, I think you are exactly right. Because we are explicitly setting the Domain and Path, the browser sees the cookies as different, though it still seems to work for persisting the session.

I am not sure if this works in browsers other than FireFox, I will need to do some more testing. If it doesn't work in at least IE6, Safari, and maybe Opera, then it may be a deal breaker. And of course, we also need to consider if there will ever be behavior change where this does result in a cookie overwrite.

One other thought that I had, that I am going to look into, is to use the Web Server (Apache or IIS) or a Web Application Firewall to modify the session cookie as it is going back to the client. In Apache, I believe this can be accomplished with mod_security for Apache, I am not sure if any of the IIS WAFs support this.
# Posted By Jason Dean | 5/7/09 9:54 AM
masterorb's Gravatar @ Jason

re: those other browsers. I have not found a good solution for tracking cookies in IE. Do you have a recommendation?
# Posted By masterorb | 5/7/09 11:14 AM
jeneli's Gravatar any reason why you havent tried this
In the jrun-web.xml put this after the </persistence-config> element.

<cookie-config>
<cookie-secure>true</cookie-secure>
</cookie-config>
# Posted By jeneli | 10/19/09 10:59 AM
Jason Dean's Gravatar @jendeli - Thanks for the comment. I should have pointed out that that is a possibility. The only problem with it, and to me it is a reason not to do it, is that doing that makes the session token secure for ever application server wide. Which would mean the every app on the server would need to be 100% SSL, I think this is a pretty rare situation.

But you are right, if you are in a situation where you can do that, then that is a great choice.
# Posted By Jason Dean | 10/19/09 11:46 AM
Jon Briccetti's Gravatar the server im working with (CF8.0) doesnt seem to be setting the jsessionid token domain - so when im using the code you have it sets 2 cookies; the other thing i've noticed is that if i use the .addHeader() java method or the <cfheader tag it seems to work the same way. is there any reason not to use <cfheader???
# Posted By Jon Briccetti | 12/7/09 1:58 PM
Jason Dean's Gravatar @jon

I don't recall if there was a reason I used addHeader() over usign cfheader, it has been a while.

As for the writing of two cookies, yeah, you need to ensure that whatever is being set by the server is what you are also setting. It seems very unusual that CF would not be setting the domain attribute at all. In fact, that seems impossible. The domain is what tells the browser to which domain it should send the cookie. If no domain was set, it would either send the cookie to ALL domains, which would be a HUGE security issue, or it would not send the cookie to ANY servers, which would make them worthless.
# Posted By Jason Dean | 12/7/09 2:46 PM
Jason Dean's Gravatar @jon,

I think I said it in the post, but I will say it again here. This method of setting the cookies is a hack, and I really don't like it and I do not know how it will behave in situations that I have not tested (like in any browser other than FF and any servers other than Apache and JRun).

I can say that in Tomcat it is actually more effective than JRun because with Tomcat the addHeader() method actually writes our cookie AFTER the tomcat cookie, so the Tomcat cookie is over-written by ours. This is the opposite of the way JRun seems to work.

Like I said, i do not like any of it. The CFID and CFToken cookies are MUCH easier to deal with here.
# Posted By Jason Dean | 12/7/09 2:49 PM
bw's Gravatar This info was helpful to secure the jsessionid, cftoken, and cfid cookies for my apps; thanks. But the problem is that the session is lost when moving between https and http (because the values in these cookies change). I really don't want to unnecessarily force the whole site https when 95% of it does not need to be.
How are you folks dealing with this problem
# Posted By bw | 3/13/10 7:59 PM
rwyland's Gravatar @Henry @Jason
It may be a bit late, but I found this to be very useful on setting up a J2EE cookie from JRUN.

http://livedocs.adobe.com/jrun/4/Programmers_Guide......

I was using your implementation in the post above and was getting a lot of issues with chrome and firefox not accepting the new cookie. As soon as I dug into it enough, I realized that instead of replacing the cookie in the App.cfc, I could simply "edit" it in JRUN by using the settings found in my link:

<cookie-config>
<cookie-secure>true</cookie-secure>
<cookie-path>/;HttpOnly</cookie-path>
</cookie-config>

You would simply add this into the jrun-web.xml file in the /WEB-INF dir.

Your tutorials are really great! Taught me everything I needed to know about sessions and cookies in CF! Thanks!
# Posted By rwyland | 7/27/10 5:02 PM
rwyland's Gravatar Sorry the link in my comment above didn't link to the correct anchor, try this:

http://livedocs.adobe.com/jrun/4/Programmers_Guide...
# Posted By rwyland | 7/27/10 5:05 PM
tony petruzzi's Gravatar awesome!!!! i'm putting this in the next version of cfwheels for sure.
# Posted By tony petruzzi | 12/11/11 8:54 AM
Gary F's Gravatar A belated thank you to Jason for working out how to set the jsessionid cookie to httponly and secure. With ever more sophisticated ways to perform session hijacking both of these cookie attributes are critical to have in place as part of a whole measure of security precautions.

One remaining issue is how to clear or reset the jsessionId when a user logs out. If you immediately log in again or as another user the jsessionId remains the same which is not a good. Even if you clear the jsessionId cookie somehow CF will reassign you the same jsessionId. The CF docs suggest:
<cfset getPageContext().getSession().invalidate()>
But this messes up the app with an invalid session error if the user wants to log in again. You'd think <cflogout> would clear everything for you, but even deleting the whole session and cookie scope isn't good enough. I hope CF11 delivers a simple solution.
# Posted By Gary F | 2/9/13 12:19 PM
Gary F's Gravatar To answer my own question (yeah, I know, sorry), you can totally reset the jsessionId using an amazingly simple method. In your logout code use Jason's technique to set the jsessionId cookie but hard code the value to 0. If someone tries to login again within the default session timeout period CF will realise the Id is invalid and assign a brand new one. Deleting the cookie itself isn't good enough, it must be set to 0.
# Posted By Gary F | 2/9/13 12:38 PM
Chris Herdt's Gravatar In my ColdFusion 11 test environment, this code created a 2nd cookie (lowercased jsessionid) instead of updating the existing cookie (JSESSIONID). I uppercased the instances of jsessionid in cfcookie tag and the header assignment statement to address this in my environment.
# Posted By Chris Herdt | 7/22/14 2:18 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner