919 - 926 - 9847

FarCry 5 maintain state using browser cookies

So, I've got a new site rolling out that we're using FarCry 5.0 (soon to be 5.1, when it's no longer beta). This particular site has a members area that's styled like facebook/myspace. In keeping with the vein of keeping the users happy, it's become apparent that most people don't want log into their social sites every time they visit them. Hmm... that's not currently built into FarCry, so it's time to dive in and play!

First up, let's extend the FarCryUD to include our new method for cookie detection. You need to make a copy of /farcry/core/packages/security/FarcryUD.cfc and copy it to /farcry/projects/{myproject}/packages/security/FarcryUD.cfc. You'll want to change the top line of the file to the following (the extends being the most important):


<cfcomponent displayname="My User Directory" hint="Provides the interface for the FarCry user directory" extends="farcry.core.packages.security.FarcryUD" output="false" key="CLIENTUD" bEncrypted="false">

Next, let's add the new cookie code detection. This adds a cfelseif into the mix. This is in the authenticate function.


<cfif structkeyexists(form,"userlogin") and structkeyexists(form,"password")>
            <!--- If password encryption is enabled, hash the password --->
            <cfif this.bEncrypted>
                <cfset form.password = hash(form.password) />
            </cfif>
            
            <!--- Find the user --->
            <cfquery datasource="#application.dsn#" name="qUser">
                select    *
                from    #application.dbowner#farUser
                where    userid=<cfqueryparam cfsqltype="cf_sql_varchar" value="#form.userlogin#" />
                        and password=<cfqueryparam cfsqltype="cf_sql_varchar" value="#form.password#" />
            </cfquery>
            <cfset stResult.userid = form.userlogin />
        <cfelseif isdefined("cookie.my_memberid") AND isdefined("cookie.my_password")>
        
            <!--- Find the user --->
            <cfquery datasource="#application.dsn#" name="qUser">
                select    *
                from    #application.dbowner#farUser
                where    userid=<cfqueryparam cfsqltype="cf_sql_varchar" value="#cookie.my_memberid#" />
                        and password=<cfqueryparam cfsqltype="cf_sql_varchar" value="#decrypt(cookie.my_password,'saltmebaby')#" />
            </cfquery>
            <cfset stResult.userid = cookie.burton_memberid />
        <cfelse>
            <ft:processform>
                <ft:processformObjects typename="#getLoginForm()#">
                    <!--- If password encryption is enabled, hash the password --->
                    <cfif this.bEncrypted>
                        <cfset stProperties.password = hash(stLogin.password) />
                    </cfif>
                    
                    <!--- Find the user --->
                    <cfquery datasource="#application.dsn#" name="qUser">
                        select    *
                        from    #application.dbowner#farUser
                        where    userid=<cfqueryparam cfsqltype="cf_sql_varchar" value="#stProperties.username#" />
                                and password=<cfqueryparam cfsqltype="cf_sql_varchar" value="#stProperties.password#" />
                    </cfquery>
                    
                    <cfset stResult.userid = stProperties.username />
                </ft:processformObjects>
            </ft:processform>
        </cfif>

Lastly, we need to modify our login process to create a cookie when the user logs in... only if there isn't already a cookie set. You would want to create a custom login for this, so copy /farcry/core/webtop/login.cfm to /farcry/projects/{myproject}/customadmin/login/login.cfm. You'll want to change the block of code that's in the final cfelse that relocates the user based on a successful login.


<cfelse>
        <!--- relocate to original location --->
        <cfif not isdefined("cookie.my_memberid")>
            <cfset stUser = createObject("component", application.stcoapi["farUser"].packagePath).getByUserID(listfirst(application.security.getCurrentUserID(),"_")) />
            <cfcookie name="my_memberid" value="#stUser.userid#" expires="30" >
            <cfcookie name="my_password" value="#encrypt(stUser.password,'saltmebaby')#" expires="30">
        </cfif>
        <cflocation url="#stResult.returnUrl#" addtoken="No">
        <cfabort>
    </cfif>

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
MCG's Gravatar He lives!
# Posted By MCG | 1/18/09 8:45 PM
Matthew Williams's Gravatar Indeed, but only just. Do you still talk to anyone at RTP?
# Posted By Matthew Williams | 1/24/09 10:05 AM
Jeremy Larter's Gravatar Thanks for the interesting article.

Would you mind clarifying why you chose to use encrypt/decrypt
for the cookie password (instead of say, hash)?
# Posted By Jeremy Larter | 1/26/09 5:29 PM
Matthew Williams's Gravatar Mostly because it's only recently come to my attention that using hash would be a better best practice ;). No other reason, really.
# Posted By Matthew Williams | 1/26/09 5:46 PM
Jeremy Larter's Gravatar @Matthew,

OK, thanks for letting me know. I am still trying to pickup the best practice myself.
One of the recent CFMeetup presentations mentioned that is also good to store a unique
salt for each hash (to mitigate against rainbow tables), and also to run the hash in a
loop at least 1000 times. Apparently, hashing has no real cost associated with the
operation.

I have been enjoying your FarCry articles, keep it up.

Regards Jeremy
# Posted By Jeremy Larter | 1/29/09 4:59 PM