Copy link to clipboard
Copied
I'm using CFML on my application. I need help with developing a logout operation that destroys a session. For now, on the logout link I'm calling the login page that's how when the BACK Button on the browser is clicked the user is still logged in.
<!---authenticationService.cfc--->
<cfcomponent>
<!---validateUser() Method--->
<cffunction name="validateUser" access="public" output="false" returntype="array">
<cfargument name="userEmail" type="string" required="true" />
<cfargument name="userPassword" type="string" required="true" />
<cfset var aErrorMessages=ArrayNew(1) />
<!---Validate the email--->
<cfif NOT isValid('email',arguments.userEmail)>
<cfset arrayAppend(aErrorMessages,'Please,provide a valid email address') />
</cfif>
<!---Validating the Password--->
<cfif arguments.userPassword EQ ''>
<cfset arrayAppend(aErrorMessages,'Please, provide a password') />
</cfif>
<cfreturn aErrorMessages />
</cffunction>
<!---doLogin() Method--->
<cffunction name="doLogin" access="public" output="false" returntype="boolean">
<cfargument name="userEmail" type="string" required="true" />
<cfargument name="userPassword" type="string" required="true" />
<!---create the isUserLoggedIn variable--->
<cfset var isUserLoggedIn=false />
<!---get the user data from the database--->
<cfquery datasource="myapp" name="getInfo">
select * from Info
where emailid='#form.userEmail#' and password='#form.userPassword#'
</cfquery>
<!---Check if the query returns one and only one user--->
<cfif getInfo.recordcount eq 1 >
<!--- log the user in --->
<cflogin>
<cfloginuser name="#getInfo.username#" password="#getInfo.password#" roles="#getInfo.role#">
</cflogin>
<!--- save user data in session scope --->
<cfset session.stLoggedInUser={'userFirstName'=getInfo.username} />
<!---change the isUserLoggedIn variable to true--->
<cfset var isUserLoggedIn=true />
</cfif>
<!---return the isUserLoggedIn variable --->
<cfreturn isUserLoggedIn />
</cffunction>
<!---doLogout() Method--->
<cffunction name="doLogout" access="public" output="false" returntype="any">
<!---delete user from session scope--->
<cfset structDelete(session,'stLoggedInUser') />
<!---log the user out--->
<cflogout />
</cffunction>
</cfcomponent>
<!---LoginForm.cfm--->
<!---Handle the logout--->
<cfif structKeyExists(URL,'logout')>
<cfset createObject("component",'authenticationService').doLogout() />
</cfif>
<!---Form processing begins here--->
<cfif structkeyExists(form,'submitLogin')>
<!---Create an instane of the authenticate service component--->
<cfset authenticationService=createObject("component",'authenticationService') />
<!---Server side data validation--->
<cfset aErrorMessages=authenticationService.validateUser(form.userEmail,form.userPassword)>
<cfif ArrayisEmpty(aErrorMessages)>
<!---Proceed to the login procedure --->
<cfset isUserLoggedIn=authenticationService.doLogin(form.userEmail,form.userPassword) >
</cfif>
</cfif>
<!---Form processing ends here--->
<cfform>
<fieldset>
<legend>Login</legend>
<cfif structKeyExists(variables,'aErrorMessages') AND NOT ArrayIsEmpty(aErrorMessages)>
<cfoutput>
<cfloop array="#aErrorMessages#" index="message" >
<p >#message#</p>
</cfloop>
</cfoutput>
</cfif>
<cfif structKeyExists(variables,'isUserLoggedIn') AND isUserLoggedIn EQ false>
<p class="errorMessage">User not found.Please try again!</p>
</cfif>
<cfif structKeyExists(session,'stLoggedInUser')>
<!---display a welcome message--->
<p><cfoutput>Welcome #session.stLoggedInUser.userFirstName# </cfoutput>
<p><a href='#'>My profile</a><a href="LoginForm.cfm?logout">Logout</a></p>
<cfelse>
<dl>
<dt>
<label for="userEmail">Email address</label>
</dt>
<dd>
<cfinput type="email" name="userEmail" required="true" >
</dd>
<dt>
<label for="userEmail">Password</label>
</dt>
<dd>
<cfinput type="password" name="userPassword" required="true" >
</dd>
</dl>
<cfinput type="submit" name="submitLogin" value="Login" />
</fieldset>
</cfif>
</cfform>
<cfdump var="#session#">
Copy link to clipboard
Copied
Forget about destroying the session. ColdFusion doesn't like doing that. In any case, there is hardly ever a case where you need to destroy the session. All you need here is for the session to correctly identify when a user is logged in or logged out.
I would do these 3 things:
1) add the following setting to Application.cfc:
<cfset loginStorage="session">
2) use object-oriented principles to ensure that each of the methods validateUser, doLogin and doLogout has just 1 responsibility. That is, each does precisely what its name says. By the time you call doLogin, you should know whether or not the user has valid credentials.
Example of code to call doLogin:
<cfif structkeyExists(form,'submitLogin')>
<!---Create an instance of the authenticate service component--->
<cfset authenticationService=createObject("component",'authenticationService') />
<!---Server side data validation--->
<cfset validationResult=authenticationService.validateUser(form.userEmail,form.userPassword)>
<!--- Check whether validationResult contains stUserCredentials --->
<cfif arrayLen(validationResult) eq 1 and isStruct(validationResult[1])>
<cfset stUserCredentials=validationResult[1]>
<!---Proceed to the login procedure --->
<cfset isUserLoggedIn=authenticationService.doLogin(stUserCredentials.validEmail,stUserCredentials.validPassword,stUserCredentials.validRole,stUserCredentials.validUsername) >
</cfif>
</cfif>
3) simplify the logic of AuthenticationService.cfc as follows:
<cfcomponent>
<cffunction name="validateUser" access="public" output="false" returntype="array">
<cfargument name="userEmail" type="string" required="true" />
<cfargument name="userPassword" type="string" required="true" />
<cfset var aValidationResult=arrayNew(1) />
<cfset var stUserCredentials=structNew() />
<!---Validate the email--->
<cfif NOT isValid('email',arguments.userEmail)>
<cfset arrayAppend(aValidationResult,'Please provide a valid email address') />
</cfif>
<!---Validate the Password--->
<cfif arguments.userPassword EQ ''>
<cfset arrayAppend(aValidationResult,'Please provide a password') />
</cfif>
<cfif arrayLen(aValidationResult) gt 0>
<cfreturn aValidationResult />
<cfelse>
<!---get the user data from the database--->
<cfquery datasource="myapp" name="getInfo">
select * from Info
where emailid=<cfqueryparam cfsqltype="cf_sql_varchar" value="#,arguments.userEmail#"> and password=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.userPassword#">
</cfquery>
<!---Validate user credentials: check if the query returns one and only one user--->
<cfif getInfo.recordcount neq 1>
<cfset arrayAppend(aValidationResult,'The e-mail or password you have provided is incorrect') />
<cfelse>
<cfset stUserCredentials.validEmail=arguments.userEmail />
<cfset stUserCredentials.validPassword=arguments.userPassword />
<cfset stUserCredentials.validRole=getInfo.role />
<cfset stUserCredentials.validUsername=getInfo.username />
<cfset arrayAppend(aValidationResult, stUserCredentials) />
</cfif>
</cfif>
<cfreturn aValidationResult />
</cffunction>
<!---doLogin() Method--->
<cffunction name="doLogin" access="public" output="false" returntype="boolean">
<cfargument name="email" type="string" required="true" />
<cfargument name="password" type="string" required="true" />
<cfargument name="role" type="string" required="true" />
<cfargument name="username" type="string" required="true" />
<!--- log the user in --->
<cflogin>
<cfloginuser name="#arguments.email#" password="#arguments.password#" roles="#arguments.role#">
</cflogin>
<!--- save user data in session scope --->
<cfset session.stLoggedInUser={userFirstName=arguments.username} />
<cfset session.isUserLoggedIn=true />
<!--- isUserLoggedIn session variable: which will automatically be false if the user is logged out elsewhere in the application --->
<cfreturn session.isUserLoggedIn />
</cffunction>
<!---doLogout() Method--->
<cffunction name="doLogout" access="public" output="false" returntype="void">
<!---delete user from session scope--->
<cfset structDelete(session,'stLoggedInUser') />
<!---log the user out--->
<cflogout />
<cfset session.isUserLoggedIn=false />
</cffunction>
</cfcomponent>
Copy link to clipboard
Copied
Actually it is important to destroy the session upon logout and CF does in fact not mind at all - there is even a function you can call to do it: sessionInvalidate()
https://cfdocs.org/sessioninvalidate
It is a security best practice to do this upon logout. If you use j2ee sessions there is a way to do that as well. Beyond that it also helps with performance because it allows the session memory to be freed up.
Copy link to clipboard
Copied
pete_freitag wrote
Actually it is important to destroy the session upon logout and CF does in fact not mind at all
You yourself have in fact confirmed my point that ColdFusion doesn't like destroying sessions. Otherwise it would have been the default behaviour, wouldn't it? There is a good reason why it is not.
The ability to invalidate sessions was only added in relatively late versions of ColdFusion - CF10, if I remember correctly. As you have hinted, sessionInvalidate doesn't work for JsessionId related sessions. That is because separate applications may share the same JsessionId. To invalidate such a session, you thererefore have to take recourse to the Java servlet API. These are the considerations that I referred to as "don't like".
Anyway, what you say may make sense, as a general principle about session use, but some caution is required in this particular case. ColdFusion's login/logout apparatus is separate from its session lifecycle. Thus, it might be uncertain where in the session lifecycle the user is logged in or logged out. In other words, logging in and logging out are finite processes which you may not assume to have completed.
That is why I would disagree with your remark, "important to destroy the session upon logout". Unless you mean, of course, that it is important to destroy the session after having verified that the user has been logged out.
What madhurib5454 could then do after the logging-out code is, for example,
<cfif getAuthUser() is ''><!--- user is logged out; invalidate and clear up current session --->
<cfset sessionInvalidate() >
</cfif>
In any case, I agree with you on the importance to security of invalidating the session. To borrow from your idea, it would even be better, security-wise, for madhurib5454 to rotate the session after logging the user in, using
<cfset sessionRotate() >
Beyond that it also helps with performance because it allows the session memory to be freed up.
The increase in performance is debatable. Has anyone ever measured it? How does the memory ColdFusion needs to clear up the session and the objects bound to it compare, say, to the memory involved in waiting another x minutes for the session to timeout, and ColdFusion automatically clearing it up then?