Mike Zavarello
Mike Zavarello

Reputation: 3554

ColdFusion: session variables not sticking in website migrated from CF 8 to CF 10

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> 

Upvotes: 1

Views: 4790

Answers (2)

Mike Zavarello
Mike Zavarello

Reputation: 3554

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.

Upvotes: 0

Brad Wood
Brad Wood

Reputation: 3953

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.

Upvotes: 3

Related Questions