Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Mark Drew and Michael Hnat
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Mark Drew ( @markdrew ) Michael Hnat ( @madmike_de )

Creating A "Remember Me" Login System In ColdFusion

By on
Tags:

The other day, I was working on a login system that had a "Remember Me" checkbox where, when checked, the user would be automatically logged back into the system upon subsequent visits to the site. I have built systems like this many times before, but for some reasons, I was hitting a mental road block; I was having trouble wrapping my head around certain parts of it, I think because the login system I was working on at the time was a little unique. I tried searching Google, but didn't really find any great examples.

Since I didn't see anything great out there, I figured I would sit down and try to write up a quick little demo application that explains one way of creating a "Remember Me" login system. I say "one way" because there are several ways to do this and different ways are more secure depending on your needs. For the majority of us out there, I would bet that when tracking login status, we are using something like a boolean flag or the existence of a user ID. So, to keep it simple, that is what I am going to do - for the following demo application, logged-in status will be determined by the existence of a SESSION-based user ID (and since I am not working with a database, that ID will be either 1 [logged in] or 0 [logged out]).

Before we look at the code, let's just think for a moment about what happens in our application when we have automatic logins. To the point, when do we want to check to see if the user is automatically logged in? This is the part that was tripping me up the other day for some reason. There's only one place in the application that we ever want to try to automatically log a user in - when their session starts. In ColdFusion, this can be hooked into use Application.cfc's OnSessionStart() event method.

Think about it - when does the session start? When the user first enters the application. And, when do we want to "remember" them? When they first show up. Once a user is already in the application, they have either logged in, or are in a logged-out status by choice. There is no need to worry about logging a user in automatically past the first page since the user's status will be determined by their actions at that point.

But what about logout? Another concept to think about - a user could logout and still be in the application past the first page visit. This is absolutely true (and is of all logout actions); however, if the user chooses to logout, should we be worrying about logging them back in automatically? Of course not; that would go against the whole gesture of logging out. So, again, we see that we only ever have to worry about the very first visit to the application, when the session starts.

That being said, let's take a look at a quick demo application. And, let's start with the Application.cfc which is where we will have hooks into the session startup event. In the Application.cfc, there's really two things to notice: first, in the OnRequest() event method, we are forcing the login page if the user is not logged in. And, secondly, we are checking for an automatic login in the OnSessionStart() event method:

<cfcomponent
	output="false"
	hint="I define the application and root-level event handlers.">

	<!--- Define application settings. --->
	<cfset THIS.Name = "RememberMeDemo" />
	<cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 5, 0 ) />
	<cfset THIS.SessionManagement = true />
	<cfset THIS.SessionTimeout = CreateTimeSpan( 0, 0, 0, 20 ) />
	<cfset THIS.SetClientCookies = true />

	<!--- Define the request settings. --->
	<cfsetting
		showdebugoutput="false"
		requesttimeout="10"
		/>


	<cffunction
		name="OnApplicationStart"
		access="public"
		returntype="boolean"
		output="false"
		hint="I run when the application boots up. If I return false, the application initialization will hault.">

		<!---
			Let's create an encryption key that will be used to
			encrypt and decrypt values throughout the system.
		--->
		<cfset APPLICATION.EncryptionKey = "S3xy8run3tt3s" />

		<cfreturn true />
	</cffunction>


	<cffunction
		name="OnSessionStart"
		access="public"
		returntype="void"
		output="false"
		hint="I run when a session boots up.">

		<!--- Define the local scope. --->
		<cfset var LOCAL = {} />

		<!---
			Store the CF id and token. We are about to clear the
			session scope for intialization and want to make sure
			we don't lose our auto-generated tokens.
		--->
		<cfset LOCAL.CFID = SESSION.CFID />
		<cfset LOCAL.CFTOKEN = SESSION.CFTOKEN />

		<!--- Clear the session. --->
		<cfset StructClear( SESSION ) />

		<!---
			Replace the id and token so that the ColdFusion
			application knows who we are.
		--->
		<cfset SESSION.CFID = LOCAL.CFID />
		<cfset SESSION.CFTOKEN = LOCAL.CFTOKEN />


		<!--- Create the default user. --->
		<cfset SESSION.User = {
			ID = 0,
			DateCreated = Now()
			} />


		<!---
			Now that we are starting a new session, let's check
			to see if this user want to be automatically logged
			in using their cookies.

			Since we don't know if the user has this "remember me"
			cookie in place, I would normally say let's param it
			and then use it. However, since this process involves
			decryption which might throw an error, I say, let's
			just wrap the whole thing in a TRY / CATCH and that
			way we don't have to worry about the multiple checks.
		--->
		<cftry>

			<!--- Decrypt out remember me cookie. --->
			<cfset LOCAL.RememberMe = Decrypt(
				COOKIE.RememberMe,
				APPLICATION.EncryptionKey,
				"cfmx_compat",
				"hex"
				) />

			<!---
				For security purposes, we tried to obfuscate the
				way the ID was stored. We wrapped it in the middle
				of list. Extract it from the list.
			--->
			<cfset LOCAL.RememberMe = ListGetAt(
				LOCAL.RememberMe,
				2,
				":"
				) />

			<!---
				Check to make sure this value is numeric,
				otherwise, it was not a valid value.
			--->
			<cfif IsNumeric( LOCAL.RememberMe )>

				<!---
					We have successfully retreived the "remember
					me" ID from the user's cookie. Now, store
					that ID into the session as that is how we
					are tracking the logged-in status.
				--->
				<cfset SESSION.User.ID = LOCAL.RememberMe />

			</cfif>

			<!--- Catch any errors. --->
			<cfcatch>
				<!---
					There was either no remember me cookie, or
					the cookie was not valid for decryption. Let
					the user proceed as NOT LOGGED IN.
				--->
			</cfcatch>
		</cftry>

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="OnRequestStart"
		access="public"
		returntype="boolean"
		output="false"
		hint="I perform pre page processing. If I return false, I hault the rest of the page from processing.">

		<!--- Check for initialization. --->
		<cfif StructKeyExists( URL, "reset" )>

			<!--- Reset application and session. --->
			<cfset THIS.OnApplicationStart() />
			<cfset THIS.OnSessionStart() />

		</cfif>

		<!--- Return out. --->
		<cfreturn true />
	</cffunction>


	<cffunction
		name="OnRequest"
		access="public"
		returntype="void"
		output="true"
		hint="I execute the primary template.">

		<!--- Define arguments. --->
		<cfargument
			name="Page"
			type="string"
			required="true"
			hint="The page template requested by the user."
			/>

		<!---
			We are going to be using the user's ID as a the way
			to check for logged-in status. Check to see if the
			user is logged in based on the ID. If they are, then
			include the requested page; if they are not, then
			force the login page.
		--->
		<cfif SESSION.User.ID>

			<!--- User logged in. Allow page request. --->
			<cfinclude template="#ARGUMENTS.Page#" />

		<cfelse>

			<!---
				User is not logged in - include the login page
				regardless of what was requested.
			--->
			<cfinclude template="login.cfm" />

		</cfif>

		<!--- Return out. --->
		<cfreturn />
	</cffunction>

</cfcomponent>

In the OnSessionStart() we are wrapping the whole cookie / automatic login system in a CFTry / CFCatch block. This is because we are working with third-party data that may not even exist. There are a lot of chances here for the code to throw an exception:

  • RememberMe doesn't exist in the COOKIE scope.
  • We try to decrypt an empty string.
  • The decrypted string does not have a second list value.

Instead of worrying about each of those conditions, I am just putting it all into a CFTry / CFCatch. That way, if an exception get's thrown, I know the value either did not exist or was not valid, and in either case, we don't want to log the user in automatically. And, since this only happens once per session, the cost of exception creation is non-existent.

In the OnRequest() event method, simply take note that if the user's ID does not have non-zero value, I am forcing the login page to be rendered regardless of what template was requested.

Now, let's take a look at the login page. I am trying to keep this simple, so I am not worrying about form errors or anything like that. In fact, this whole application lacks error handling - I am trying to keep the signal to noise ratio very high.

<!--- Param the form values. --->
<cfparam name="FORM.username" type="string" default="" />
<cfparam name="FORM.password" type="string" default="" />
<cfparam name="FORM.remember_me" type="boolean" default="false" />

<!---
	Check to see if the form has been submitted. Since we
	are trying to keep this low-level, just check to see
	if both values match up.
--->
<cfif (
	(FORM.username EQ "big") AND
	(FORM.password EQ "sexy")
	)>

	<!---
		The user has logged in. This is where we would do the
		authorization; however, since we are just running a very
		simple demo app, simply give the user an ID of "1" to
		signify that they have logged in.
	--->
	<cfset SESSION.User.ID = 1 />


	<!--- Check to see if the user want to be remembered. --->
	<cfif FORM.remember_me>

		<!---
			The user wants their login to be remembered such that
			they do not have to log into the system upon future
			returns. To do this, let's store and obfuscated and
			encrypted verion of their user ID in their cookies.
			We are hiding the value so that it cannot be easily
			tampered with and the user cannot try to login as a
			different user by changing thier cookie value.
		--->

		<!---
			Build the obfuscated value. This will be a list in
			which the user ID is the middle value.
		--->
		<cfset strRememberMe = (
			CreateUUID() & ":" &
			SESSION.User.ID & ":" &
			CreateUUID()
			) />

		<!--- Encrypt the value. --->
		<cfset strRememberMe = Encrypt(
			strRememberMe,
			APPLICATION.EncryptionKey,
			"cfmx_compat",
			"hex"
			) />

		<!--- Store the cookie such that it never expires. --->
		<cfcookie
			name="RememberMe"
			value="#strRememberMe#"
			expires="never"
			/>

	</cfif>


	<!--- Redirect to root. --->
	<cflocation
		url="./"
		addtoken="false"
		/>

</cfif>


<cfoutput>

	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
	<html>
	<head>
		<title>Login</title>
	</head>
	<body>

		<h1>
			Application Login
		</h1>

		<form action="#CGI.script_name#" method="post">

			<label>
				Username:
				<input type="text" name="username" size="20" />
			</label>
			<br />
			<br />

			<label>
				Password:
				<input type="password" name="password" size="20" />
			</label>
			<br />
			<br />

			<label>
				<input type="checkbox" name="remember_me" value="1" />
				Remember Me
			</label>
			<br />
			<br />

			<input type="submit" value="Login" />

		</form>

	</body>
	</html>

</cfoutput>

If the user logs into the system and has selected the "Remember Me" option, then we create the obfuscated and encrypted value and store that as a cookie that never expires. We are doing so much to the ID value since it does pose a security risk. Again, this is not the most complex example of how this functionality can be built. But, by altering the value of the stored ID, we are at least making it extremely difficult for users to go in and mess with the value in an attempt to login as a different user.

When the user logs out, the logic is simple:

<!---
	When logging out, we want to both log out the current user
	AND make sure that they don't get automatically logged in
	next time.
--->

<!---
	Since our application is using the User ID to keep track
	of login status, let's reset that value.
--->
<cfset SESSION.User.ID = 0 />

<!---
	We also don't want the user to be automatically logged
	in again, so remove the client cookies.
--->
<cfcookie
	name="RememberMe"
	value=""
	expires="now"
	/>


<!--- Now that the user has been logged out, redirect. --->
<cflocation
	url="./"
	addtoken="false"
	/>

All we have to do is clear the session flag and erase the cookie.

Then, I just had an index page to make sure the application was working properly:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
	<title>Remember Me Login Demo</title>
</head>
<body>

	<h1>
		Remember Me Login Demo
	</h1>

	<p>
		Welcome to the "Remember Me" login demo application.
		If you are seeing this page, then you have successfully
		logged into the system (or were logged in automatically
		using your cookies).
	</p>

	<p>
		<a href="logout.cfm">Logout</a>
	</p>

</body>
</html>

That's all there is to it. Again, there are more secure ways of doing this and better ways of building login systems in general. But, the ideas will be the same and the places in which we run our logical checks will also be the same.

Want to use code from this post? Check out the license.

Reader Comments

55 Comments

you lost me at: "Store the CF id and token. We are about to clear the
session scope for intialization and want to make sure
we don't lose our auto-generated tokens."

Why do use use LOCAL again?

15,640 Comments

@Henry,

LOCAL is a "var"ed variable that created a pseudo local scope for the function. It's really just a short hand. For example,

<cfset var a = 1 />
<cfset var b = 2 />

... is the same as:

<cfset var LOCAL = {
a = 1,
b = 2
} />

I am just focusing my "var" to one variable, and then housing other local variables inside of it.

As far as the clearing of the CFID and CFTOKEN, the OnSessionStart() can be launched manually from the OnRequestStart() method depending on URL parameters. Therefore, we need to clear the SESSION if we are manually resetting the data. Of course, we don't want to lose the CFID / CFTOKEN values as these are part of the core ColdFusion session management framework. So, we store them temporarily, clear the SESSION, and then "recreate" them.

55 Comments

@Ben,

Although the example works wonderfully, it is not the easiest example to follow because of code length. My brain ran out of ram the first time reading your code.

Oh well, the verboseness of CFML is not your fault. :)

Maybe I should have copy and paste the code into CFEclipse then read the code. It's hard to read so much CFML in a browser.

15,640 Comments

@Henry,

Most definitely. When reading it in a blog post, not only is there a lot of peripheral data (the whole site), there is also a mixture of code and explanation that intertwine. Certainly a lot to take in, especially when there are multiple code files in question.

I don't know if this would be different in any language as there really isn't a lot going on here when you boil it down. It's just a matter of getting comfortable with all the different moving parts.

41 Comments

I like that you're including salt with the encrypted rememberMe cookie (the CreateUUID stuff), that's a good way to keep nosy people out of there, and drive up the length of the encrypted text to something not so easily brute-forceable.

It's not as portable, but I also like to include a unique (eg, hash(CreateUUID()) )token on the account. In that way, even if the encryption key is cracked (which might not be too hard with cfmx_compat), you still can't spoof another user.

Sometimes I like to include an encoded expiry too, such that perhaps I only honor a given RememberMe cookie for a certain number of weeks so that even if the cookie is captured, eventually it expires anyway.

So for example: "#CreateUUID()#:#SESSION.User.Id#:#SESSION.User.Token#:#DateFormat(now(), 'YYYY-MM-DD')#:#CreateUUID()#"

15,640 Comments

@Eric,

I like the idea of including the Hash() as well to compare the unecrypted value to. Very slick. That hadn't even occurred to me. But you're right - even if someone did crack the encryption (and I'm told that cfmx_compat is nothing special), it would make is extremely hard to spoof a different value.

2 Comments

I tried this and get an error:
Element CFID is undefined in SESSION.

when calling the line:
<cfset LOCAL.CFID = SESSION.CFID />

for the first time

15,640 Comments

@Peggy,

I have run into that problem before, but to be honest, I am not sure that I have ever really debugged it successfully. I usually get it very occasionally on a site that has high-traffic.

Are you still getting it today?

2 Comments

For Peggy, Chad, and Ben, I just got that error.

Do you have J2EE Session Variables on? I switched out the CFID and CFTOKEN for JSESSIONID and I get a little farther.

2 Comments

@Garth Colasurdo,

Nevermind, I'm still bombing out when the session times out. If I do make it past the CFID, it still has trouble with the Session.User.ID.

I'm having a devil of a time with session variables these days.

33 Comments

Hi Ben,

Need help.

I have an application with login and logout features.

I am going mad because when I am logging out and then hit the back button in the browser, the page again goes back to the previous page.
It seems like the session variables were not cleared o deleted on logging out.

I have used both structclear and structdelete but without any success.

It will be of great help if you can show how to create a logout feature in cold fusion perfectly.

Also I am using the application.cfc.

Thanks,

Abhijit

32 Comments

@Abhijit

I believe that deals with page caching. Try adding these two lines to the top of your pages that you don't want them to be able to go back too

<CFHEADER Name='Expires' Value='#Now()#'>
<CFHEADER NAME='pragma' VALUE='no-cache'>

15,640 Comments

@Abhijit,

The Back-button is an interesting issue because the browser doesn't want to pull a new page if it doesn't have to. What you fill find is that if you try to DO anything from that back-pack, the system will then give you the "hey you're not logged in" message.

Dan's suggestion is good, but I would be careful about putting it on all your pages as you will probably end up forcing the browser to pull back more pages than it needs to perhaps? Not to say that that's bad, especially in a dynamic site, but just think about where you put it.

I don't have a great solution to this. Sorry.

32 Comments

@Ben

Thanks for clarifying that it doesn't need to go on every page.

From my understanding I don't believe both of these are necessarily needed, but more used as a precaution if one fails. Is the case that these will work separately but recommended to be used together?

15,640 Comments

@Daniel,

I *believe* you are correct in that they are redundant, but fail-safes for the various browsers. In honesty, I don't work a lot with page expiration management. However, from what I have looked into, I think the Expires value actually needs to be in HTTP timestring format, which is a bit different than just passing in Now()... but again, my experience with this is mostly R&D, not practical:

www.bennadel.com/blog/1619-Caching-ColdFusion-Pages-With-Expires-Header-Value.htm

32 Comments

@Ben,

I just took a quick look at the live docs for cf8 and it use

<cfheader name="Expires" value="#GetHttpTimeString(Now())#">

as an example. Looking back at some of the older docs it has changed. I know 6.1 just used the Now()

And I am with you too, I haven't used it in a practical situation yet, but thinking about it I can understand why as I believe the value is passed directly into the html to be evaluated by the browser. I can assume some browsers may work with it, but unless it actually meets the standards all of them probably wont.

Note: This is more from my previous knowledge and none of it has really been tested, so take away what you want from it.

39 Comments

There are various tricks you can use to get around the back button letting a user think they're logged in when they're not. Some of them are better than others. Using cache control headers is a popular one, but doesn't always succeed (as there are corporate proxies, and even public ISP transparent proxies which may be configured to violate cache policies).

A simple fix (but which I strongly recommend against - I simply mention it for completeness) is to use <script>window.location.forward();</script> on every page. This completely breaks the back button for anyone who has scripting enabled.

A better solution is to use an ajax call for this purpose. For the sake of simplicity, assuming you're using jQuery and jQueryUI, you can do something like this:

<cfif Session.Authenticated>
<script>
$(document).ready(function(){
$.ajaxSetup({ cache: false });
$.getJSON("remote/session.cfc", {method: 'checkAuthenticated', returnFormat: 'json'}, function(json) {
if (!json) {
var mb = $('#notAuthenticated');
mb.dialog({
modal: true,
closeOnEscape: false,
resizable: false,
stack: true,
autoOpen: false,
bgiframe: true,
title: "Your session has expired.",
buttons: {
"Log In": function(){
window.location = "index.cfm?fa=Auth.LoginPrompt";
}
}
}).html("Your session has expired, or you have logged out");
mb.dialog("open");
// Hide the close X button in the dialog (need to do after opening the dialog for IE6)
mb.parent().find('.ui-dialog-titlebar-close').css({ display: 'none' });

} /end if
}); //end json
}); //end document.ready
</script>
<div id='notAuthenticated'></div>
</cfif>

You'd have a related CFC at remote/session.cfc:
<cfcomponent>
<cffunction name='checkAuthenticated' access='remote' returntype='boolean'>
<cfif StructKeyExists(SESSION, 'Authenticated') and SESSION.Authenticated>
<cfreturn true>
<cfelse>
<cfreturn false>
</cfif>
</cffunction>
</cfcomponent>

32 Comments

@Eric,

That leads me into a jQuery question as I am just now looking into jQuery as I am about to use it for a project at work. And since you were talking about ways that other methods could fail.

Would this still work if the user has javascript disabled?

39 Comments

No, jQuery is purely client side scripting. Users running the NoScript plugin or who otherwise have javascript disabled would not receive notification. The only option I'm aware of for such users is the cache controls.

Such users are likely to be more savvy users though (for what that's worth), so my belief is that they are less likely to be the sort who runs into this problem and are confused by it.

32 Comments

@Eric,

That is what I thought. I just didn't want someone to have the impression that either way can be counted on 100% of the time. There are several ways you can try to accomplish the same thing that will work certain times, but there is not really away to guarantee that a user will not be able to go back and look at the pages that I know of.

39 Comments

You're right, there is no way you can guarantee that the user is unable to view the old pages in their browser history (eg if you're trying to protect against a different user snooping their browser history). If it's a security concern to have old pages accessible (eg, they contain sensitive information such as bank or medical details), you definitely want to use cache control headers like you mentioned earlier (though I recommend you use a very old timestamp instead of now() to avoid issues with disparity between the client and server clock). The jQuery and other client side solutions are purely for user experience and not for security.

Modern browsers should honor Pragma: no-cache (IE 5.x is the most recent browser to my knowledge which does not honor this), but as I mentioned, there's always the concern that there's a transparent or opaque proxy which interferes with the caching policy you specify.

This is why many banking sites open the secure portion of their page in a new window, which is then closed as part of the deauthentication process. This way there's not back-button history (though there may still be disk cache).

Bruce Schneier recently spoke of deauthentication and how not enough focus is put on this important security concern in the industry at large: http://threatpost.com/blogs/difficulty-un-authentication-128 . He wasn't speaking specifically to the web, but the same concerns apply.

1 Comments

Hello Ben,

Thank you for posting this. I tried to modify your application using the following code:

Instead of:
"<cfif ((FORM.username EQ "big") AND(FORM.password EQ "sexy"))>"

I checked to see if the form was submitted...
<cfif IsDefined("Form.login") AND Form.UserPass NEQ "" AND Form.UserName NEQ "">

I ran a query to check credentials against a db table...

Then I added:

<cfif q_CheckLogin.RecordCount EQ 1>
<cfset SESSION.User.ID = "q_CheckLogin.UserRole" />
</cfif>

My question is, does SESSION.User.ID have to be 0/1 for this app to function? It works fine for me after this change... but I'm not sure if I should insert a userrole condition for a session id... does it impact security if I were to introduce a couple of variables if authentication succeeds? For example, a userID based on the db entry and a userrole?

15,640 Comments

@Mohamad,

The user ID does not have to be a 1/0 value. In my applications, I typically give non-users / non-logged-in users an ID of zero (0). Then, anyone who is logged in or is a valid user has an ID of greater-than zero.

Since zero is a false value and non-zero is a true value, then it can be used in boolean logic.

As far as this line:

<cfset SESSION.User.ID = "q_CheckLogin.UserRole" />

You have to be careful to use a string value. Strings are not true/false in ColdFusion (as far as I can remember - you'd have to double-check that).

22 Comments

Thanks Ben for the links, albeit there a bit over my head, I've asked my hosting provider for help on this one :)

4 Comments

=) jejeje, excuse me buddy, i was googling searching for what it means

<cfif StructKeyExists( URL, "reset" )>

i didnt found anything.. i mean , you never defined "reset" in the URL structure, is it a CF predefined value?

what it means...

:S sorry for beginer questions, jejeje

3 Comments

@Samuel -- I'm pretty sure that simply means that is a url-scope variable called 'reset' exists, do the following.

3 Comments

Ben - got a weird problem, and can't figure it out. Am hoping you or other subscribers can point me in the right direction... I've got CF8 Standard installed locally, pointing at a SQL2000 db.

I've modified your login example to query a db for a password, based on a username passed in via URL.

When I hit the app using:

http://localhost/index.cfm?uname=DougTest

everything works like a charm. But when I attempt the same using my PCs IP address:

http://12.34.567.89/index.cfm?uname=DougTest

it stops working... url.uname is immediately dropped and I'm routed to my invalid login page.

Any thoughts on why using my machine's IP address would cause the application to work differently than using localhost?

15,640 Comments

@Samuel,

As @Doug suggested, all it does is check to see if a key named "reset" exists in the URL scope. Meaning, was "reset" passed as a URL parameter in the page request.

@Doug,

Ahhh, yeah - not setting cookies will get you ever time :) Glad you got it sorted.

1 Comments

Ben,

Thanks for this great script! I'm noticing though that the Remember Me feature only seems to work for 24 hours (I did not change any other values in your example). When I come back the next day, the login page is requesting credentials again, and I'd like to avoid that if possible (which is why I wanted the Remember Me feature in the first place). Using CF 9.0.1... any thoughts on what I might be missing?

2 Comments

Ben, This is a great article and I was able to integrate CFLOGIN and CFNTAUTHENTICATE by modifying the code slightly. I thought I'd share for those who are using Windows Authentication.

Application.cfc - Change

<!--- Param the form values. --->
<cfparam name="FORM.username" type="string" default="" />
<cfparam name="FORM.password" type="string" default="" />
<cfparam name="FORM.remember_me" type="boolean" default="false" />

<!---
Check to see if the form has been submitted. Since we
are trying to keep this low-level, just check to see
if both values match up.
--->
<cfif SESSION.User.ID eq 0>
<!---User is not logged in - include the login page regardless of what was requested.--->
<cfinclude template="login.cfm" />
<cfelse>
<!--- User logged in. Allow page request. --->
<cfinclude template="#ARGUMENTS.Page#" />
</cfif>

Login.cfm - Change

<cfif ((FORM.username NEQ '') AND (FORM.password NEQ ''))>

<!---
The user has logged in. This is where we would do the
authorization; however, since we are just running a very
simple demo app, simply give the user an ID of "1" to
signify that they have logged in.
--->
<cflogin>
<cfset theusername=form.username>
<cfset thepassword=form.password>
<cfntauthenticate username="#theusername#" password="#thepassword#" domain="yourdomain" result="authresult" listgroups="yes">

<cfif authresult.auth>
<!--- Log user in to ColdFusion and set roles to the user's Groups. --->
<cfloginuser name="#theusername#" password="#thepassword#"
roles="#authresult.groups#">

<cfset SESSION.User.ID = "#theusername#"/>


<!--- Check to see if the user want to be remembered. --->
<cfif FORM.remember_me>

<!---
The user wants their login to be remembered such that
they do not have to log into the system upon future
returns. To do this, let's store and obfuscated and
encrypted verion of their user ID in their cookies.
We are hiding the value so that it cannot be easily
tampered with and the user cannot try to login as a
different user by changing thier cookie value.
--->

<!---
Build the obfuscated value. This will be a list in
which the user ID is the middle value.
--->
<cfset strRememberMe = (
CreateUUID() & ":" &
SESSION.User.ID & ":" &
CreateUUID()
) />

<!--- Encrypt the value. --->
<cfset strRememberMe = Encrypt(
strRememberMe,
APPLICATION.EncryptionKey,
"cfmx_compat",
"hex"
) />

<!--- Store the cookie such that it never expires. --->
<cfcookie
name="RememberMe"
value="#strRememberMe#"
expires="never"
/>

</cfif>


<!--- Redirect to root. --->
<cflocation
url="./"
addtoken="false"
/>

<cfelse>
<!--- The user was not authenticated.
Display an error message and the login form. --->
<cfoutput>
<cfif authresult.status IS "AuthenticationFailure">
<!--- The user is valid, but not the password.
<h2>The password for #theusername# is not correct<br>
Please Try again</h2>--->
<cfelse>
<!--- There is one other status value, invalid user name.
<H2>The user name #theusername# is not valid<br>
Please Try again</h2> --->
</cfif>
</cfoutput>
</cfif>
</cflogin>
</cfif>

2 Comments

Sorry, Accidentally added extra info for Application.cfc.

Application.cfc - Change

<cfif SESSION.User.ID eq 0>
<!---User is not logged in - include the login page regardless of what was requested.--->
<cfinclude template="login.cfm" />
<cfelse>
<!--- User logged in. Allow page request. --->
<cfinclude template="#ARGUMENTS.Page#" />
</cfif>

39 Comments

I would recommend, if you can swing it, to avoid <cfntauthenticate> and configure IIS to require Integrated Windows Authentication. This is WAY more secure, and actually quite a lot easier.

If you have IIS configured for IWA, authentication happens before ColdFusion even begins to execute. By the time your code runs, you know they're a valid user, and their user credentials are stored in CGI.REMOTE_USER. You don't even have to check if they're logged in, if they're not, their browser will prompt them (or authenticate them transparently if they either use IE or have Firefox/Chrome configured for transparent authentication).

This way too, the username and password aren't sent in plain text over the network. Even if you don't use SSL, someone snooping on a user connection won't be able to see either their username or password.

2 Comments

@peggy and @ben

Peggy mentioned that she's getting asession.cfid does not exist - If she has J2EE session vars on, then ID and TOKEN are not part of the structure directly, you can get to them in the URLTOKEN.

That is the most likely cause of SESSION.CFID not being found - IF session management is ON...

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel