There are nuances. You'll need to supply an image for the link to the login page. The home page removes an existing pass (if any) from the cookie to avoid confusion. The welcome mat directory (mat) has an Application.cfm that enables session management, sets a datasource, and provides a key to encrypt the pass for the cookie. If the user's session times out, or the user blunders into a forbidden area, a bouncer (mat/bounce.cfm) tells the user what happened and returns the user to the login. For this demo, meeting.cfm says hello to prove that your login has been successful.
<!--- home.cfm ---> <a href="mat/image.cfm"> <img src="earth.gif" width="60" height="60" border="0"></a><br> ! SPLASH !
Browsing pages in the mat directory includes ../mat/Application.cfm. In ../mat/Application.cfm, name an application, turn session management on, but turn the associated cookies off so you can set your own temporary ones. Set the datasource; set a pass key.
<!--- mat/Application.cfm ---> <!--- Enable session management ---> <cfapplication name="LoginInc" sessionmanagement="yes" setclientcookies="no"> <!--- Set datasource and pass key ---> <cfset request.Db="context"> <cfset request.PassKey="useyourown">
Make image.cfm handle login. Why name it image.cfm? To keep the login a surprise. It won't fool anyone looking for it, but the name supports the initial air of wonder that people have when they reach your site.
First, set defaults by expiring cookies and defining username and password as empty.
<!--- mat/image.cfm --->
<!--- Set defaults --->
<cfcookie name="Pass" value="0" expires="-1">
<cfset Message="">
<cfif not isDefined("form.UserName")>
<cfset Username="">
<cfset Password="">
</cfif>
Next, act on a login request that's been submitted (if any). Query the table. If a row is found, include ../mat/ok.cfm, include ../act/menu.cfm, and use cfabort to stop processing this page. If not, complain.
<!--- Act on login --->
<cfif isDefined("form.Try")>
<cfquery name="loginGet" datasource="context">
select * from LoginInc
where Username = '#form.Username#'
and Password = '#form.Password#'
</cfquery>
<cfif loginGet.recordcount>
<!--- Issue pass --->
<cfinclude template="../mat/ok.cfm">
<!--- Move to menu --->
<cfinclude template="../act/menu.cfm">
<cfabort>
<cfelse>
<!--- Complain --->
<cfset Message="Incorrect Login">
</cfif>
</cfif>
Finally, accept a login attempt. Later on, you could add a link to get back to the splash page. But, this will do for now.
<!--- Accept login ---> <cfoutput>#Message#</cfoutput> <form name="login" action="../mat/image.cfm" method="post"> <strong>Please log in . . . </strong><br> Username <input type="text" name="Username" size="25" maxlength="25" value=<cfoutput>"#Username#"</cfoutput>><br> Password <input type="password" name="Password" value=<cfoutput>"#Password#"</cfoutput>> <input type="submit" name="Try" value="Go"> </form>
Include ../mat/ok.cfm to issue credentials. Because mat/Application.cfm enabled session management, a session has already been assigned. So, read the current session (cfid and its partner, cftoken). Then, store the Userid and Role in memory for this session.
<!--- mat/ok.cfm ---> <!--- Get and modify session ---> <cflock scope="session" timeout="30" type="exclusive"> <cfset request.cfid=session.cfid> <cfset request.cftoken=session.cftoken> <cfset session.Userid=loginGet.Userid> <cfset session.Role=loginGet.Role> </cflock>
When this is done, store Userid, cfid, and cftoken in a urlEncoded, encrypted cookie along with some random padding to discourage the curious. (The urlEncoding ensures that the result is viewable during debugging.) Out of seven items in the string, only items 2, 4, and 6 are useful. The rest are padding that will vary within separate ranges. Because Randomize doesn't live up to its name in my environment - it cause randRange to return the same number over and over - I don't invoke randomize first. Simply provide the range in which the number should fall. When all the pieces of the list have been defined, encrypt the whole list with the pass key in mat/Application.cfm. You could encrypt it multiple times, but this will do for this demonstration.
<!--- Write padded, encrypted, encoded cookie ---> <cfscript> P1=randRange(0,150); P2=loginGet.Userid; P3=randRange(200,350); P4=request.cfid; P5=randRange(400,550); P6=request.cftoken; P7=randRange(600,750); PassRaw="#P1#,#P2#,#P3#,#P4#,#P5#,#P6#,#P7#"; </cfscript> <cfset request.Pass=urlEncodedFormat(encrypt(PassRaw,request.PassKey))> <cfcookie name="Pass" value="#request.Pass#">
Before leaving this page, consider that the first step after leaving this page will be to include act/menu. All pages browsed in the act directory will use the cookie and session memory to determine userid and role. However, the menu isn't browsed the first time; it's included and therefore won't see the cookie. The page is still loading. Therefore, you need to provide credentials it can read(request.Role and request.Userid) for the first time the page is used.
<!--- Provide first-page credentials ---> <cfset request.Role=loginGet.Role> <cfset request.Userid=loginGet.Userid>
<!--- act/menu.cfm ---> <!--- Block if needed ---> <cfparam name="request.Role" default=""> <cfif request.Role does not contain "A"> <cfoutput>#Message#</cfoutput> <cfset Message="You cannot perform this function."> <cfinclude template="../mat/bounce.cfm"> <cfabort> </cfif> <!--- Act on choices ---> <cfparam name="form.Pick" default=""> <cfif form.Pick is "Exit"> <cfinclude template="../mat/image.cfm"> <cfabort> <cfelseif form.Pick contains "Meeting"> <cfinclude template="../act/meeting.cfm"> <cfabort> </cfif> <!--- Provide menu ---> <form name="menu" action="../act/menu.cfm" method="post"> <input type="submit" name="Pick" value="Meeting Schedule"> <input type="submit" name="Pick" value="Exit"> </form>
Browsing any page in the act directory includes act/Application.cfm, which identifies the user and looks up the user's privileges. This code must set defaults, read the cookie, enable session management based on the session in the cookie, get ColdFusion to read that session from memory, and confirm that the user identified by the cookie is the same user identified in memory for this session.
To begin, set the datasource, set a pass key, and set a good default status.
<!--- act/Application.cfm ---> <!--- Set defaults ---> <cfset request.Db="context"> <cfset request.PassKey="useyourown"> <cfset SessionOK=1>
Try to read the Pass: an encoded, encrypted, and padded cookie value. Decode it. Decrypt it with the pass key. If the result is not a list with seven items, declare the session bad. The second item should be the Userid, the fourth the cfid, and the sixth the cftoken. Tell ColdFusion that it has found the cfid and cftoken in the url. (These won't actually show up in the URL.) When you subsequently enable session management, it will find them there and try to "continue" the session they represent.
<!--- Try to read an encoded, encrypted, padded cookie --->
<cfparam name=cookie.Pass default="">
<cfif len(trim(cookie.Pass))>
<cfset PassRaw=decrypt(urlDecode(cookie.Pass),request.PassKey)>
<cfif listLen(PassRaw) is 7>
<cfset request.UseridPass=listGetAt(PassRaw,2)>
<cfset url.cfid=listGetAt(PassRaw,4)>
<cfset url.cftoken=listGetAt(PassRaw,6)>
<cfelse>
<cfset SessionOK=0>
</cfif>
<cfelse>
<cfset SessionOK=0>
</cfif>
Enable session management, but don't let ColdFusion write its default cookies. (You want to use only the Pass that you wrote at login.) Because ColdFusion will see the url variables you have just "written", it will look in memory for the session they represent. Read session variables into request variables (so you only have to lock them once). If you can't read the session, declare the session bad.
<!--- Enable session management --->
<cfapplication name="LoginInc"
setclientcookies="no"
sessionmanagement="yes">
<!--- Try to read memory --->
<cflock scope="session" timeout="30" type="readonly">
<cfif isDefined("session.Userid")>
<cfset request.cfid=session.cfid>
<cfset request.cftoken=session.cftoken>
<cfset request.Userid=session.Userid>
<cfset request.Role=session.Role>
<cfelse>
<cfset SessionOK=0>
</cfif>
</cflock>
Compare the Userid from the cookie with the Userid in session memory. If they don't match, declare the session bad.
<!--- Compare Userids from two sources --->
<cfif SessionOK>
<cfparam name="request.Userid" default=0>
<cfparam name="request.UseridPass" default=-1>
<cfif request.UseridPass is not request.Userid>
<cfset SessionOK=0>
</cfif>
</cfif>
If the session is bad, bounce the user. To do this, create a message that tells the user what you're doing. Include ../mat/bounce.cfm (which will display that message). Use cfabort to end processing of this page.
<!--- Bounce the user if there's a problem ---> <cfif not SessionOK> <cfparam name="request.Userid" default=0> <cfparam name="request.cfid" default=0> <cfparam name="request.cftoken" default=0> <cfparam name="request.Role" default=""> <cfset Message="Your session is not active; you will need to log in again."> <cfinclude template="../mat/bounce.cfm"> <cfabort> </cfif>
The code for mat/bounce.cfm is simple. It's just a form. However, having all unexpected exits call this page provides the opportunity for a consistent interface.
<!--- mat/bounce.cfm ---> <cfoutput>#Message#</cfoutput> <form action="../mat/image.cfm" method="post"> <input type="submit" name="doit" value="OK"> </form>
As is usual with most journeys, the destination is less imposing than the trip. Here's act/meeting.cfm. If the role isn't appropriate, it bounces the user. (You may want to stop in place with cfabort and display "press your back button to continue" instead of kicking the user out of the application.) If the user wants to exit, the page sends the user to the menu. Otherwise, it confirms success and provides a button that lets you leave when you're done admiring your handiwork.
<!--- act/meeting.cfm ---> <!--- Block if needed ---> <cfparam name="request.Role" default=""> <cfif request.Role does not contain "A"> <cfset Message="You cannot perform this function."> <cfinclude template="../mat/bounce.cfm"> <cfabort> </cfif> <!--- Act on choices ---> <cfparam name="form.Exit" default=""> <cfif len(trim(form.Exit))> <cfinclude template="../act/menu.cfm"> <cfabort> </cfif> <!--- Your function goes here ---> HELLO. You made it. <form action="meeting.cfm" method="post"> <input type="submit" name="Exit" value="goodbye"> </form>
To apply these ideas to your own work, a few pointers are in order. Notice that when the menu is first included that the cookie isn't set yet. So, feed the menu what it would have received from Application.cfm in the working directory. When the menu or other pages in the working directory are browsed, Application.cfm reads the session identification from the encrypted cookie, tells ColdFusion it was in the URL, causes ColdFusion to read that session from memory, and makes sure the Userid in the cookie matches the Userid in memory. Avoid using cflocation; cfinclude lets you work with cookies more reliably.
The subject of security comes up whenever login and access are mentioned. Unless you expect the user to log in again for each page, you need to pass some kind of token from page to page, and this is pretty secure. Unless the encryption is broken - you can use more creative encryption than the demo uses - the worst that might reasonably happen is that an individual physically copies a cookie from someone else's machine. It would have to happen while that someone else was still using it; because, the cookie is erased when the browser closes. A stolen cookie will be still be useless unless that someone else currently has an active session AND that session is the same session as identified by the cookie. Remember that sessions in memory time out. If the Userid and session in the cookie don't match an active session that's already using that Userid, no access is gained.
This application doesn't set permanent cookies; it doesn't post a visible URL. In the absence of plain session identification in URLs or cookies, ColdFusion provides a fresh session each time (until it runs out and recycles session numbers). Even if a user logs in several times in succession, the user will receive a different session each time. Note that someone trying to add session identification to the actual URL can't force a specific session to be used in an effort to use just part of the cookie credential; because, any session identification in the URL is overwritten by the session identification in the cookie, and inability to read the cookie blocks access. Therefore, an unscrupulous person can't count on cookie theft (which requires close timing and an unlikely degree of physical access in the first place) to get into your application.
Now you're without excuses to add login where appropriate. Login is included. =Marty=