I am very new to ColdFusion. I am building a very basic system in which I now need to set a session variable, in case of successful login. But I don't know how to set session in ColdFusion or how to check it on application pages.
I searched for solution but could not find satisfactory solution. I need some example in which a session is set in case of login, and destroyed on logout.
I've read about Application.cfm, but where this file is located?
This could be a simple question and may be repeating in some ways.
Thanks.
First of all you should probably use:
Application.cfc
You can either use:
OnSessionStart
In which case you don't need to lock your session variables, as Coldfusion takes care of this.
If you set your session variable outside of this method, you may need to lock the variable like:
Before login:
<cfif NOT StructKeyExists(session,"authenticated")>
<cflock scope="session" timeout="30" type="exclusive">
<cfset session.authenticated = false />
</cflock>
</cfif>
New account creation:
When a user logs in, remember to use something like BCrypt() to hash the password & store in DB. Don't encrypt passwords as these can be unencrypted and this can create a potential security loophole.
https://github.com/tonyjunkes/BCryptCFC
<cfset salt = BCrypt.genSalt()>
<cfset hash = BCrypt.hashString("password", salt)>
Login validation:
After a user has logged in, use BCrypt() to check whether the clear text password matches the password hash in your DB:
<cfset BCrypt.checkString("password", hash)>
Also check that the 'username' [e-mail] matches...
If BCrypt() validation is successful, then set your 'session' variable:
<cflock scope="session" timeout="30" type="exclusive">
<cfset session.authenticated = true />
</cflock>
This is a very basic implementation of how the login should work, but it gives you an idea of how to get started.
Another tip, is that, if you are locking your 'session' variables, then instead of having to continually use:
<cflock>
When reading from the session, it would be advisable to convert your 'sesssion' variable into the 'variables' scope, like:
<cflock scope="session" timeout="10" type="readOnly">
<cfset variables.lckauthenticated = session.authenticated />
</cflock>
You can then use:
variables.lckauthenticated
Outside of the <cflock> tag.
Now, there is some debate as to whether you need to lock 'session' variables, but my rule, and one that is recommended in Adobe's Official Documentation, is to lock 'session' variables, if you are reading & writing to them outside of onSessionStart.
Then, when your user logs out, just set:
<cflock scope="session" timeout="30" type="exclusive">
<cfset session.authenticated = false />
</cflock>
You can then use this flag, to determine what is displayed to your user.
I usually set several 'session' variables during a successful login, like:
session.userid
session.roleid
There are other things like 'session' rotation, which help to safeguard your session, but these are more advanced topics for another post...
Related
I am trying to set a coldfusion user object for each person as they log in. Similar to how rails has devise and I can call current_user.id or current_user.username from wherever I am in the site. I have a users table that stores roles and other user information. I want to query that and assign all of the fields to a sort of global user object. I can then use that for components, page display etc.
I am trying to figure out where and how to do this. I have tried initializing a component like this in onsessionstart, onrequeststart etc, but when I try to reference globalUser.id in a component for instance it hits a 500 error because globalUser is not defined.
<cfset globalUser = CreateObject( "component",
"controllers.user" ).globalUser(empidname= '#getauthuser()#') />
Any recommended ways to do this? Any plugins that provide functionality like this?
You want to use session variables.
<cffunction name="onSessionStart">
<cfset session.globalUser = CreateObject( "component",
"controllers.user" ).globalUser(empidname= '#getauthuser()#') />
...
</cffunction>
In order for sessions to be enabled, you have to alter application.cfc
component {
this.name = "AppName";
this.sessionManagement = true;
CFBuilder admin storage
15cdb5dcb6.jpg
Application.cfm
34ed7586e1.jpg
Login.cfm
<cfif not isDefined('FORM.submitButton')>
<cfform name="loginForm" method="post" action="#CGI.SCRIPT_NAME#">
Login:
<cfinput type="text" name="login" required="yes">
Password:
<cfinput type="password" name="password" required="yes">
<br>
<cfinput type="submit" name='submitButton' value="Sign">
<br>
<cfinput type="button" name='registerButton' value="Register">
</cfform>
<cfelse>
<cfquery name='getUser' datasource="dbfortest">
SELECT * FROM usertable WHERE login="#FORM.login#" ;
</cfquery>
<cfif getUser.RecordCount NEQ 0>
<cfif FORM.password eq getUser.password>
<cflock scope="Session" timeout="60" type="exclusive" >
<cfset Session.loggedIn = "yes">
<cfset Session.user = "#FORM.login#">
</cflock>
<cfoutput>#StructKeyList(Session)#</cfoutput>
<cfelse>
Your pass isn't correct.
</cfif>
<cfelse>
There is no user with this name.
</cfif>
</cfif>
part of page when i want to use login including.
<cfif Session.loggedIn eq "no">
<cfinclude template="login.cfm">
</cfif>
<cfif structKeyExists(session, "user")>
<cfoutput>Welcome, #Session.user#.</cfoutput>
</cfif>
<cfoutput>#StructKeyList(Session)#</cfoutput>
Hello everyone, please help me understand these sessions' behavior.
The whole problem consists in attempting to pass variables from one page to another.
So after login i don't see the session.user in session struct.
How can i pass this?
Have already tried different browsers.
#Aquitaine has given you some good information. I just wanted to also point out that another part of your problem is likely that you have set a 10 second life span for your sessions. That's probably not long enough.
In the Application.cfm example that you posted you have this line:
sessiontimeout="#createTimespan(0,0,0,10)#"
The arguments for the CreateTimeSpan function are as follows:
createTimespan(days, hours, minutes, seconds)
As such you are assigning a 10 second lifespan for sessions. Perhaps you meant to set 10 minutes instead of 10 seconds.
To figure out what's going on with the session variables, try putting in some debug code right after your cfset session statements to make sure that they're happening. Maybe <cfdump var="#session#">.
You do not need to cflock your session scope (and have not needed to since CFMX). See Adam Cameron's 2013 post on when to lock scopes
If your debug code runs and you see the session variables, but then they're gone on the next page, that may be an issue with your session storage (which is a different part of cfadmin) or else whatever front-end webserver you're using. Try <cfdump var="#session#"> in onRequestStart in Application.cfc and make sure that JSESSIONID is the same on every request. (or try disabling J2EE session variables in CFADMIN and see if the same problem persists with CFID/CFTOKEN).
If your debug code doesn't run, then you should be seeing one of your error conditions.
For ease-of-reading, be consistent in your casing when refering to scopes, e.g. session not Session. While this kind of thing may not matter functionally, it can get you into trouble with portability when referencing paths or components.
Some other issues:
If you are going to use a boolean value for loggedIn then use a boolean value: true or false or 1 or 0 or (if you must) yes or no but not "yes" which is a string; instead of being able to do if (session.loggedIn) if you will have to do if (session.loggedIn == 'yes') and nobody will be happy.
If this is meant to be working, production site code, at a minimum you need to be using cfqueryparam as you do not ever want to pass unescaped user input directly to a database query.
You might also head over to the CFML slack at cfml.slack.com and ask on #cfml-beginners for some pointers on writing login forms.
I am running Lucee 5.1.3.18/Tomcat/centOS/mySql (3 physical,6 virtual) and I am having erratic session loss. I have looked through and verified it isn't bad code doing this. The situation is a user adds items to a cart (all items are joined on the session_id). They fill out the payment information, credit card etc... on a checkout page. Generally if you wait 3 to 5 minutes and submit to the review it throws an error not seeing these items (session_id changed). The time frame varies but it is usually around 5 minutes.
This happens when I have Lucee admin set up to use my datasource and store session info in the DB.
application.cfc:
<cfset THIS.Name = "sessionName" />
<cfset THIS.SessionManagement = true />
<cfset THIS.ClientManagement = true />
<cfset THIS.ApplicationTimeout = CreateTimeSpan(0,12,0,0) />
<cfset THIS.SessionTimeout = CreateTimeSpan(0,4,0,0) />
<cfset THIS.SetClientCookies = true />
<cfset THIS.SetDomainCookies = false />
<cfset THIS.ScriptProtect = true />
<cfset THIS.sessionType = "jee">
<cfset THIS.sessionStorage = "myDatasource">
<cfset THIS.sessionCluster = true>
Changing
<cfset THIS.sessionType = "jee">
to cfml, also has the same problem (tried EHcache to w/ no success).
If I switch to use "Memory" and eliminate DB, I have the issue still however much less. Using "Memory" also makes the heap swell and eventually the servers lock up.
The logs don't show anything helpful, but I have been seeing broken pipe errors from time to time and db connection loss also. I account that to the server locking up though.
I'm not trying to ask an open ended question but do you have any advice on likely issues you have encountered. Is there obscure Lucee specific settings that I may have overlooked? Any help is appreciate.
Thanks,
Henry
You could take a look to see if you can find either of these calls below in your code somewhere as maybe they are ending your sessions early.
sessionInvalidate would kill of the session (i'm not too sure if it's immediate or after the request that called it has finished) and the setMaxInactiveInterval call overrides the session timeout used in the application.cfc.
<cfscript>
getPageContext().getSession().setMaxInactiveInterval(javaCast("int", 60));
sessionInvalidate();
</cfscript>
I am using ColdFusion 9.0.1
I am taking over a site and the guy before me created about 100 variables and put them into the APPLICATION scope. I believe that his 100 variables were continuously being overwritten with each page load.
Basically, he had this in Application.cfc:
APPLICTION.VariableOne = "SomeStringOne";
APPLICTION.VariableTwo = "SomeStringTwo";
APPLICTION.VariableThree = "SomeStringThree";
My plan is to keep it simple and yet very readable is to test for a specific structure in the application scope. If it's not there, create the structure and variables:
if (not isDefined("APPLICTION.AppInfo") or not isStruct(APPLICTION.AppInfo)) {
APPLICTION.AppInfo = structNew();
APPLICTION.AppInfo.VariableOne = "SomeStringOne";
APPLICTION.AppInfo.VariableTwo = "SomeStringTwo";
APPLICTION.AppInfo.VariableThree = "SomeStringThree";
}
Of course, once the site is live and we are done creating all of the application variables, I'd move this into the into the onApplicationStart() method.
The solution that I want has to be more about "readability" and less about "efficiency". Several non-CFers, but very experience coders will be using this and will need to "get it" quickly.
Does my plan have any gaping holes or is it too inefficient?
Is there a more readable way of creating and managing application variables?
Why not move the definition into onApplicationStart() right now? If you need to reset them during development, you could always pass in a URL variable to flag it for reset, like so:
<!--- in Application.cfc --->
<cffunction name="onRequestStart">
<cfif IsDefined("url.resetApp")>
<cfset ApplicationStop()>
<cfabort><!--- or, if you like, <cflocation url="index.cfm"> --->
</cfif>
</cffunction>
Actually, after re-reading the OP, and reading the suggested solutions, I'm going to have to agree with the OP on his setup, for this very important reason:
This, in onApplicationStart()
APPLICTION.AppInfo = structNew();
APPLICTION.AppInfo.VariableOne = "SomeStringOne";
APPLICTION.AppInfo.VariableTwo = "SomeStringTwo";
Can then later be turned into this, within onRequestStart()
<cflock name="tmp" type="readonly" timeout="15">
<cfset REQUEST.AppInfo = APPLICATION.AppInfo />
</cflock>
Your app can then go on to access the REQUEST vars conveniently, esp, if you decide you want to cache CFCs in the same scope--they would simply go into a separate key:
APPLICATION.Model.MyObject = CreateObject('component','myobject');
Which, of course, also gets poured into REQUEST (if you choose)
Want to go Jake Feasel's route above? No problem:
<cfif isDefined('URL.reload')>
<cfset APPLICATION.Model = StructNew() />
</cfif>
Now you're able to flexibly kill your object cache but maintain your vars (or vice versa as you choose).
This is a great setup for another reason: If you want to build in your own Development/Production "mode", in which the development mode always recompiles the CFCs, but the production mode keeps them cached. The only change you have to make on top of this, is the REQUEST set noted above:
<cfif (isProduction)>
<cflock name="tmp" type="readonly" timeout="15">
<cfset REQUEST.AppInfo = APPLICATION.AppInfo />
</cflock>
<cfelse>
<cfset REQUEST.AppInfo = StructNew() />
<cfset REQUEST.AppInfo.VariableOne = "SomeStringOne" />
...etc...
</cfif>
You can also make the setting of vars and the creation of objects into a private method within Application.cfc, for even further convenience.
I would go ahead and just use OnApplicationStart but back in the pre Application.cfc days we used to do something like Application.Build and if the Build value was different then we did all of our sets on Application variables. So quick and dirty would be something like:
<cfparam name="Application.Build" default="" />
<cfset Build = "28-Nov-2011" />
<cfif Application.Build IS NOT Variables.Build OR StructKeyExists(URL, "Rebuild")>
<cfset Application.Build = Variables.Build />
<!--- A bunch of other CFSETs --->
</cfif>
This method though was something we used back when all we had was the Application.cfm
I'm seriously considering moving away from CF8 cflogin because it is tied to the server that spawned the login. In a load balanced environment you're stuck with sticky sessions if you don't do a custom implementation.
Does anyone have any source that mimics CFLogin that writes to and is managed from the client scope? Maybe even a design that matches up well with a rename replace on isuserin[any]role.
What should I be thinking about when I consider writing a replacement implementation for CFLogin?
Here is a basic non cflogin approach using variables stored in the CLIENT scope. We use a similar approach for non-sticky sessions across our server cluster behind our load balancer.
This code should live in Application.cfc -> onRequestStart() method:
<!--- handle login *post* --->
<cfif structKeyExists(FORM, "pageaction") and FORM.pageAction eq "adminlogin">
<!--- attempt to log user in --->
<cfif loginSuccessful>
<!--- Set client variables for session management --->
<cfset CLIENT.lastHit = now() />
<cfset CLIENT.loggedIn = 1 />
<!--- redirect to home page --->
<cfelse>
<!--- redirect to login page with message --->
</cfif>
<!--- all other requests, except for the login page --->
<cfelseif structKeyExists(CLIENT, "lasthit") and structKeyExists(COOKIE, "cfid") and structKeyExists(CLIENT, "cfid") and listLast(CGI.SCRIPT_NAME, "/") neq "login.cfm">
<!--- Check for timeout --->
<cfif (datediff("n", CLIENT.lastHit, now()) lte 10) and (CLIENT.loggedIn is 1) and (CLIENT.cfid is COOKIE.cfid)>
<!--- record last hit --->
<cfset CLIENT.lastHit = now() />
<cfelse>
<!--- timeout! redirect to login page --->
<cflocation URL="http://mydomain/login.cfm" addtoken="false" />
</cfif>
</cfif>
There is user role stuff, but I hope this helps as a starting point.
I customized the CF Login Wizard through Dreamweaver to be portable and to use a db table for authentication and role management. Because of this,I can use it either as a single-user login, or multiple account logins. I never have used cflogin and haven't needed to. I just drop the files into the directory, customize the login credentials, and that is it. Works perfect every time.