1

I manage a ColdFusion-based website that recently migrated from CF 8 to CF 10. The site requires users to log in and keeps certain values in session variables, which are used throughout the site for verification, etc.

Since the migration to CF 10, I have been having a lot of trouble with sessions not "sticking" from page to page, particularly after the login process. I had not been using cookies to keep track of values on the client side prior to the migration, nor do I use addtoken="yes" for my cflocation tags (I'd prefer to keep the CFID and CFTOKEN values out of the URL).

I've been doing a lot of research on this, but am struggling with a solution.

What I am doing wrong or missing? Should I not have Application.cfm set default session values? Should I do this on the login page?

For reference, current Application.cfm reads as follows (certain values changed for security purposes):

<cfapplication name="APPLICATIONNAME" 
applicationtimeout="#createtimespan(0,6,0,0)#" 
clientmanagement="yes" 
datasource="DATASOURCENAME" 
loginstorage="session" 
scriptprotect="all" 
sessionmanagement="yes" 
sessiontimeout="#createtimespan(0,1,0,0)#" 
setclientcookies="yes">

<cfif not structKeyExists(cookie,"cfid")>
    <cfcookie name="cfid" value="#session.cfid#" expires="never" domain="#cgi.SERVER_NAME#" path="/PATH">
    <cfcookie name="cftoken" value="#session.cftoken#" expires="never" domain="#cgi.SERVER_NAME#" path="/PATH">
</cfif>

<cflock timeout="5" throwontimeout="no" type="exclusive" scope="session">
        <cfif structKeyExists(session,"SESSIONVARA")>
            <cfscript>StructUpdate(session,"SESSIONVARA","DEFAULTVALUE");</cfscript>
        <cfelse>
            <cfscript>StructInsert(session,"SESSIONVARA","DEFAULTVALUE");</cfscript>
        </cfif>
        <cfif structKeyExists(session,"SESSIONVARB")>
            <cfscript>StructUpdate(session,"SESSIONVARB","DEFAULTVALUE");</cfscript>
        <cfelse>
            <cfscript>StructInsert(session,"SESSIONVARB","DEFAULTVALUE");</cfscript>
        </cfif>
        . . .
</cflock>

My current login.cfm page reads as follows (again, certain values changed):

<!--- check whether this variable was passed to this page --->
<cfif isdefined("form.username") and form.username is not "">

    <!--- generate a hashed password from the user's entry --->
    <cfset HashedPassword = hash(form.Password,"SHA-1")>

    [ SQL QUERY TO CHECK USER'S CREDENTIALS ]

    <cfif SQLQUERY.RecordCount is not 0>
        <cfset sessionRotate()>
        <cflock scope="session" type="exclusive" timeout="2">
            <cfset session.SESSIONVARA = QUERYVALUEA>
            <cfset session.SESSIONVARB = QUERYVALUEB>
            . . . 
        </cflock>

        <!--- put after sessionRotate() to keep IDs consistent --->
        <cfif structKeyExists(cookie,"cfid")>
            <cfset cookie.cfid = session.cfid>
            <cfset cookie.cftoken = session.cftoken>
        <cfelse>
            <cfcookie name="cfid" value="#session.cfid#" expires="never" domain="#cgi.SERVER_NAME#" path="/PATH">
            <cfcookie name="cftoken" value="#session.cftoken#" expires="never" domain="#cgi.SERVER_NAME#" path="/PATH">
        </cfif>

        <cflocation url="main.cfm" addtoken="no">
    <cfelse>
        <!--- no matching credentials --->
        <cflocation url="login-failed.cfm" addtoken="no">
    </cfif>

<!--- if there is no user name defined in the set of form variables, this is probably a spider or bot; reject it --->
<cfelse>
    <cflocation url="index.cfm" addtoken="no">
</cfif>

Edit: And, lastly, here is the include that checks each user and kicks them to a timeout page if they are not authorized:

<cflock scope="session" type="readonly" timeout="2">
    <cfif not structKeyExists(session,"SESSIONVARA")>
        <cflocation url="TIMEOUTPAGE" addtoken="no">
    </cfif>
</cflock> 
Community
  • 1
  • 1
Mike Zavarello
  • 3,514
  • 4
  • 29
  • 43
  • Have you tried J2EE session variables. The setting is in the coldfusion administrator. I know there is an issue with IE specifically and session variables not sticking and the work around is to use J2EE session variables. – Alan Bullpitt Jul 23 '14 at 14:54
  • In both your Application.cfm and login page, what is the purpose of the structKeyExists, since it appears you still set the values regardless. In fact the entire block in Application.cfm could be shortened to this: session.SESSIONVARA = "DEFAULTVALUE"; session.SESSIONVARB = "DEFAULTVALUE"; since it always sets the default value. That seems problematic right there. Also, most of your session scope locking is *probably* unnecessary. Can you show the code that enforces your security? – Brad Wood Jul 23 '14 at 16:57
  • @BradWood: Thanks for your reply. I started using structKeyExists vs. simple assignments to see whether that would help with the session and cookie variables. I also realized that having the default session declarations in application.cfm was effectively resetting the variables every time a user visited a new page, so I moved these inside login.cfm so they would only be declared there. The session is invalidated and cookies cleared on my logout page. As far as "enforcing my security," do you mean what code kicks out unauthorized users? – Mike Zavarello Jul 23 '14 at 17:25
  • @AlanBullpitt I've seen reference to J2EE session variables in some of the articles I've been reading, but am not sure how to use them. I don't personally have access to CFAdmin, but I'll ask our server technician about it. Thank you. – Mike Zavarello Jul 23 '14 at 17:28
  • Yes, I meant the code that is kicking out the user. So, did moving the default values fix the issue? There should be no reason why you CAN'T do that in Application.cfm, but you only need half of the if statement-- the half where they AREN'T already defined. If that was the issue, I'll put it in an answer so you can accept it. – Brad Wood Jul 23 '14 at 17:38
  • @BradWood: The code that kicks out the user is: ... this checks against one of the session variables that should always be defined and boots out the user if it's not declared. – Mike Zavarello Jul 23 '14 at 17:43
  • Add that code to the question. – Brad Wood Jul 23 '14 at 18:05

2 Answers2

3

Your code in Application.cfm will always set the default value regardless of whether it was previously set. Since you are checking for the existence of the session variable to determine if the user is logged in, I would recommend completely removing the code in Application.cfm that sets/updates the session variables. That way, it won't exist until the login is successful.

Now, if other places in your application also want to use those same session variables, I would recommend setting to a default value only if they don't exist. This is most easily done with the following code (no locking required):

<cfparam name="session.sessionvara" default="defaultvalue">
<cfparam name="session.sessionvarb" default="defaultvalue">

Then change your security check to inspect the value of those variables instead of whether or not they exist.

Brad Wood
  • 3,863
  • 16
  • 23
  • Thank you very, very much for this answer. Moving that code off of application.cfm went a long way to solve my problem. From what I recall from past iterations of ColdFusion, any time you referenced a session variable, it needed to be enclosed in cflock, but I see in one of your earlier comments that may not be necessary ... I had been setting local variables from the session variables to use on a specific page. Is this cfparam method more sound or secure? – Mike Zavarello Jul 23 '14 at 18:14
  • 1
    Locking of session and server scopes is an old practice from the VERY first versions of CF which was written in C I think and had internal consistency issues when multiple threads set the same variables. This hasn't been an issue since MX6 and the Java rewrite of CF but there are still people who do it "just because". Locking is ONLY necessary if you need consistency between multiple variables. Cfparam is the exact same as an existence if statement and a set, it's just a lot less to type and read :) – Brad Wood Jul 23 '14 at 19:00
0

Thanks to everyone who has responded to this question. I believe I have solved my initial problem. There were several pieces to this puzzle, and I wanted to elaborate on them here for future reference.

First and foremost, as @BradWood had stated, moving my default session declarations out of application.cfm helped considerably. I was basically resetting my own values on each page. My application.cfm now looks like this:

<cfapplication name="SITENAME" 
applicationtimeout="#createtimespan(0,6,0,0)#" 
clientmanagement="no" 
datasource="DATASOURCENAME" 
loginstorage="session" 
scriptprotect="all" 
sessionmanagement="yes" 
sessiontimeout="#createtimespan(0,1,0,0)#" 
setclientcookies="no">

I did some more digging on the cookies piece and came across an article from Ben Nadel about using session-only cookies: http://www.bennadel.com/blog/1131-ask-ben-ending-coldfusion-session-when-user-closes-browser.htm. I liked this approach, as I'd rather have the cookies end with the session, and my site has no need to remember anyone (folks only visit every six months or so to take evaluations). With this in mind, I set both the clientmanagement and setclientcookies attributes to "no".

My login.cfm page now looks like this. I moved all of the session and cookie initialization here from application.cfm:

<!--- check whether this variable was passed to this page --->
<cfif isdefined("form.username") and form.username is not "">

    <!--- generate a hashed password from the user's entry --->
    <cfset HashedPassword = hash(form.Password,"SHA-1")>

    [ SQL QUERY TO CHECK USER'S CREDENTIALS ]

    <cfif SQLQUERY.RecordCount is not 0>
        <cfset sessionRotate()>
        <cflock scope="session" type="exclusive" timeout="5">
            <!--- check to see if a user session has been started by looking for one of the variables --->
            <cfif structKeyExists(session,"SESSIONVARA")>
                <cfscript>
                    StructUpdate(session,"SESSIONVARA",VALUEA);
                    StructUpdate(session,"SESSIONVARB",VALUEB);
                    StructUpdate(session,"SESSIONVARC",VALUEC);
                    StructUpdate(session,"SESSIONVARD",VALUED);
                    StructUpdate(session,"SESSIONVARE",VALUEE);
                </cfscript>
            <cfelse>
                <cfscript>
                    StructInsert(session,"SESSIONVARA",VALUEA);
                    StructInsert(session,"SESSIONVARB",VALUEB);
                    StructInsert(session,"SESSIONVARC",VALUEC);
                    StructInsert(session,"SESSIONVARD",VALUED);
                    StructInsert(session,"SESSIONVARE",VALUEE);
                </cfscript>
            </cfif>
        </cflock>
        <!--- Set session-only cookies to help the site remember credentials. 
        Don't set an expire value in order to prevent CF from creating client cookies.
        If a cookie already exists, let's use that one and update the CFID and CFTOKEN 
        values to match the new session set in sessionRotate(). Also, don't declare the 
        "path" attribute in cfcookie: it will create a duplicate set of cookies and 
        confuse the site as to which is the right one. --->
        <cfif structKeyExists(cookie,"cfid")>
            <cfset cookie.cfid = session.cfid>
            <cfset cookie.cftoken = session.cftoken>
        <cfelse>
            <cfcookie name="cfid" value="#session.cfid#" domain="#cgi.SERVER_NAME#">
            <cfcookie name="cftoken" value="#session.cftoken#" domain="#cgi.SERVER_NAME#">
        </cfif>

        <cflocation url="main.cfm" addtoken="no">

    <cfelse>
        <cflocation url="login-failed.cfm" addtoken="no">
    </cfif>

<!--- if there is no user name defined in the set of form variables, this is probably a spider or bot; reject it --->
<cfelse>
    <cflocation url="index.cfm" addtoken="no">
</cfif>

Finally, here is my logout.cfm page that clears out the session-only cookies:

<cfscript>
    StructDelete(cookie,"cfid",true);
    StructDelete(cookie,"cftoken",true);
</cfscript>
<!--- force cookies to expire --->
<cfcookie name="cfid" domain="#cgi.SERVER_NAME#" expires="now">
<cfcookie name="cftoken" domain="#cgi.SERVER_NAME#" expires="now">
<!--- force session to end --->
<cfset sessionInvalidate() />
<cflocation url="index.cfm" addtoken="no">

All of this has worked very well so far in clearing up my session problems. I hope all of this information will prove helpful for others with similar issues.

Update (7-27-2015): I've revised this additional explanation to include the two cfcookie lines that force the cookies to expire immediately. Over the year since I posted this information, I had noticed that users would run into problems due to duplicate cookies. If a user logged back in without clearing their browser cookies or closing their browser altogether, the cookies from their previous session would be retained. This caused a conflict where the system would see two pairs of cookies with the same name and get confused. Forcing that expiration date of "now" effectively killed off the old cookies and restored sanity to the site.

Mike Zavarello
  • 3,514
  • 4
  • 29
  • 43