Simple Object Oriented Security in ColdFusion (Version 2)

Some thoughts on implementing simple object oriented security in ColdFusion. This entry is a result of great feedback I received from my previous entry.

What primary objects do we need?

There are two main objects that we need - User and SecurityService. There are a couple of other objects that will assist with the security functions and we will discuss them shortly.

The User object

First, we need a User object to represent the person who is logging in. The User object will be stored in the session scope.

The SecurityService object

The Security Service knows how to authenticate a person and create a User. We really only need one copy of the security service object so we will store it in the application scope.

In this example, the security service only has one function, getAuthenticatedUser(username,password), which returns an empty string if the authentication failes, otherwise returns a User object.

Using the Security Service

Let's start with how the security service might be used. Suppose that the security service has been created within the Application.cfc (or Application.cfm) and is available from the application scope as

application.securityService

And suppose we have the two following files:

index.cfm – this is our page that needs to be password protected.

login.cfm – this is our page that displays the login form.

In our index.cfm file we might have some code at the top of the page such as

<!--- A person is considered to be logged in if their
user object exists in the session --->

<cfset isLoggedIn = structKeyExists(session,"user")>
<cfif not isLoggedIn>
   <cflocation url="login.cfm">
</cfif>

The login.cfm page that displays our login form:

<form action="login.cfm" method="post">
   Username <input type="text" name="username"><br>
   Password <input type="password" name="password">
   <input type="submit" value="Login">
</form>

When this form is submitted, it goes back to the same page passing the username and password. We might use the security service at the top of the login.cfm page as follows:

<!--- Holds an error message if the login is not successful --->
<cfset errorMessage = "">

<!--- Check if the login form was submitted --->
<cfif structKeyExists(form,"username")>
   <!--- Try to get an User object --->
   <cfset user = application.securityService.getAuthenticatedUser( form.username, form.password )>
   <cfif isObject(user)>
      <!--- Login was successful, so save it to the session scope --->
      <cfset session.user = user>
      <cfoutput>
         Login successful!<br>
         <a href="index.cfm">Continue</a>
      </cfoutput>
      <cfabort>
   <cfelse>
      <cfset errorMessage = "Your login details were not correct. Please try again">
   </cfif>
</cfif>

<cfif len(errorMessage) gt 0>
   <cfoutput>
      <p>#errorMessage#</p>
   </cfoutput>
</cfif>

Implementing the Security Service

The security service is not very complicated to write for a simple security scenario.

We will introduce a UserDAO object here, which will provide us with access to User information from the database. In particular, the UserDAO has one function that we need here, readByUsername(), which returns a single User object.

We can provided this to the SecurityService.cfc via the init() function.

<cfcomponent output="false">

   <cfset variables.userDAO = "">

   <cffunction name="init" output="false">
      <cfargument name="UserDAO">
      <cfset variables.userDAO = arguments.userDAO>
      <cfreturn this>
   </cffunction>

   <cffunction name="getAuthenticatedUser" output="false">
      <cfargument name="username">
      <cfargument name="password">
      <cfset var user = variables.userDAO.readByUsername(arguments.username)>
      <cfif user.getPassword() eq arguments.password>
         <cfreturn user>
      </cfif>
      <cfreturn "">
   </cffunction>

</cfcomponent>

What if your passwords are encrypted?

Let's assume that your passwords are encrypted using a one way encryption – ie, they cannot be decrypted. Then you can create a new component Encryptor.cfc to handle the encryption, and suppose this component has function encryptString() available.

Then your getAuthenticatedUser code might be modified as follows:

<cffunction name="getAuthenticatedUser" output="false">
   <cfargument name="username">
   <cfargument name="password">
   <cfset var user = variables.userDAO.readByUsername(arguments.username)>
   <cfset var encryptor = createObject("component","Encryptor").init()>
   <cfset var encryptedPassword = encryptor.encryptString(arguments.password)>
   <!--- Compare the two encrypted passwords --->
   <cfif user.getPassword() eq encryptedPassword>
      <cfreturn user>
   </cfif>
   <cfreturn "">
</cffunction>

If the encryptor was used more that this, then perhaps if could be passed in as an additional argument to the SecurityService init() function.

Implementing other security related functions

Suppose you needed some additional security functions, such as changing passwords or checking if a person is in a given role. We can include these functions within the User object:

user.changePassword(oldPass, newPass) - returns true if the password was changed successfully, else false.

user.hasRole(role) - returns true if the user has the specified role, else false.

However, we really don't want to put security implementation details into the User object. It would be nicer if the User object is not aware of how the security is actually implemented, so we put this functionality into a separate object instead.

This means that when you call user.changePassword(), the user object then simply passes this on (delegates) to the helper object to actually do the work.

Lets call this helper our UserSecurity object.

Implementing the UserSecurity object

The user security object is a nice place to put all user security related operations. This object also needs the UserDAO, so lets pass that in as part of the init() function.

<cfcomponent output="false">

   <cfset variables.userDAO = "">

   <cffunction name="init" output="false">
      <cfargument name="UserDAO">
      <cfset variables.userDAO = arguments.userDAO>
      <cfreturn this>
   </cffunction>

   <cffunction name="changePassword" output="false">
      <cfargument name="user">
      <cfargument name="oldPass">
      <cfargument name="newPass">
      <cfif user.getPassword() eq arguments.oldPass>
         <!--- The current passwords match, so now update
         the password in the database --->

         <cfset arguments.user.setPassword(arguments.newPassword)>
         <cfset variables.userDao.save(arguments.user)>
         <cfreturn true>
      </cfif>
      <cfreturn false>
   </cffunction>

   <cffunction name="hasRole" output="false">
      <cfargument name="user">
      <cfargument name="role">
      <!--- Get a list of roles for this user --->
      <cfset var roles = variables.userDao.getUserRoles(arguments.user)>
      <cfif listFind(roles,argument.role) gt 0>
         <cfreturn true>
      </cfif>
      <cfreturn false>
   </cffunction>

</cfcomponent>

Implementing our User object

Now that we have a UserSecurity object, we can let the User object know about it. In this example we pass it in as part of the init() function.

<cfcomponent output="false">

   <cfset variables.userSecurity = "">

   <cffunction name="init" output="false">
      <cfargument name="userSecurity">
      <cfset variables.userSecurity = arguments.userSecurity>
      <cfreturn this>
   </cffunction>

   <cffunction name="changePassword" output="false">
      <cfargument name="oldPass">
      <cfargument name="newPass">
      <cfreturn variables.userSecurity.changePassword(this,arguments.oldPass,arguments.newPass)>
   </cffunction>

   <cffunction name="hasRole" output="false">
      <cfargument name="role">
      <cfreturn variables.userSecurity.hasRole(this,arguments.role)>
   </cffunction>
   
   <!--- Other User functions here ... --->

</cfcomponent>

Summary

So here are the objects and functions we used

UserDAO
- readByUserName(username)
- getUserRoles(user)

UserSecurity
- changePassword(user,oldPass,newPass)
- hasRole(user,role)

User
- changePassword(oldPass,newPass)
- hasRole(role)

SecurityService
- getAuthenticatedUser(username,password)

Creating these objects may not be easy

Unfortunately creating some of these objects may not be very convenient because they all depend on being initialised with other objects (eg the User needs to know about the UserSecurity object).

A very nice way of handling this is by using ColdSpring or LightWire. The idea here is they take care of object creation for you and 'automatically' pass in the dependant objects. Definitely worth a look.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
3 Jun 2009 01:44PM
Göran said:
Göran's Gravatar Hi,
I have tried to implement your OO security as part of my process to learn OO.
I use no framework just Transfer ORM and cfc:s.

First I created a UserDAO that gets the user by transfers readByPropertyMap.

I put the UserDAO and the SecurityService in the Appplication.cfc OnApplicationStart method as singeltons (application scope).

In the SecurityService I pass both the username (in my case e-mail) and password to the UserDAO (therefore the readByPropertyMap). I don´t know if it´s best to search only in the user field or both the user and password field, It felt better to restrict the query as much as possible.

Before my modification of the SecurityService I tested it with your set up, and found that if you don´t fill in anything in the login fields you still get validated, and a user object is created (Probably empty. Dump just shows an empty transfer object). So I added LEN(arguments.password) to check the length in the SecurityService and don´t return a user object if it has no length. Maybe a Transfer thing, how it creates objects.

Because I use Transfer and no framework I think I don´t have the need for dependency injection in this simple case.

Great example anyway!
4 Jun 2009 01:09AM
Kevan Stannard's Gravatar Hi Göran

Searching on both userEmail and userPassword sounds good to me.

I understand that if Transfer can't find a matching record then it returns an empty User object. You might like to check the userId on the object returned from the userDAO to see if the user was found.

For example, in the SecurityService object:

<cffunction name="getAuthenticatedUser" output="false">
<cfargument name="username" type="string" required="true">
<cfargument name="password" type="string" required="true">
<cfset var user = variables.userDAO.readByUsernameAndPassword(arguments.username,arguments.password)>
<cfif user.getUserId() gt 0>
<cfreturn user>
<cfelse>
<cfreturn "">
</cfif>
</cffunction>
4 Jun 2009 03:48PM
Göran said:
Göran's Gravatar Yes I found that it´s mentioned in the API Reference that it returns an empty object.

Yes! To check for the userid works great! I was first thinking of some kind of RecordCount solution, but this is better.

To avoid having transfer methods everywhere I did a generic TransferDAO that wraps the transfer functions I am currently using, instead of having UserDAO, CompanyDAO etc. I also try to have generic but descriptive names for the methods such as list, delete, create and of course readByPropertyMap (maybe quite generic anyway). Now I have only one class to deal with for Transfer things, and generic data access metodh names, that I don´t have to change if I change ORM.
5 Jun 2009 04:46PM
Kevan Stannard's Gravatar Hi Göran

It might still be a good idea to still keep all of your separate UserDAO, CompanyDAO etc, but to have them all extend your TransferDAO. This way they inherit all of the base TransferDAO functionality, but also allow you to add custom functionality specific to that object.

For example, your CompanyDAO may have a function delete() that might do some extra work first before asking the parent TransferDAO to delete the company record.

Another thought; you might like to rename your TransferDAO to BaseDAO to keep it even more generic.
23 Sep 2009 06:57PM
Stephen Weyrick's Gravatar Hi Kevan-

Thanks for writing this post. I am currently trying to implement something similar at work, and I was having a tough time visualizing how exactly all of the objects should be laid out. This was a nice reference for me since I have only begun taking the plunge into OOP world. :)
23 Sep 2009 07:18PM
Kevan Stannard's Gravatar Hi Stephen, thanks.
Add a comment
(will not be published)
(include http://)