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
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:
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:
<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.
<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:
<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.
<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.
<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.
