Implementing Simple Object Oriented Security in ColdFusion

I have written up a new version of this entry which has some better techniques for implementing ColdFusion security.

Presumably, an object oriented technique for handling security would start with a single component that provides all of the functions we need, such as validating a login, testing if a person is currently logged in, and allowing a person to logout.

Suppose we create a new object called SecurityService.cfc, and this has the following functions:

isLoggedIn() - returns a boolean indicating if a user is logged in

login( username, password ) - attempts to log the user in

logout() - logs the user out.

Now, if we store this component in the 'application' scope, then we can reuse it for all pages within our application.

My plan is for the each person's security details to be stored within their session. This means that we need to tell the security service which user/session we are dealing with. This is simply done by passing the current session in to each of our functions.

isLoggedIn( session )

login( session, username, password )

logout( session )

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

<cfset isLoggedIn = application.securityService.isLoggedIn( session )>
<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")>
   <cfset application.securityService.login( session, form.username, form.password )>
   <cfif application.securityService.isLoggedIn( session )>
      <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.

Notice that I am passing in a datasource name to the security service for the moment.

<cfcomponent output="false">

   <cfset variables. dsn = "">

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

   <cffunction name="login" output="false">
      <cfargument name="session">
      <cfargument name="username">
      <cfargument name="password">
      <cfset var q = "">
      <cfquery name="q" datasource="#variables.dsn#">
         select *
         from Users
         where
            username = '#arguments.username#'
            and password = '#arguments.password#'
      </cfquery>
      <cfif q.recordCount eq 1>
         <cfset arguments.session.userId = q.UserId>
      </cfif>
      <cfreturn isLoggedIn( arguments.session )>
   </cffunction>

   <cffunction name="isLoggedIn" output="false">
      <cfargument name="session">
      <cfreturn structKeyExists( arguments.session, "userId" )>
   </cffunction>

   <cffunction name="logout" output="false">
      <cfargument name="session">
      <cfset structClear(arguments.session)>
   </cffunction>

</cfcomponent>

In this example, our security service is accessing the database directly. This can be done much more nicely by providing a 'data access object' (DAO) to do that for you.

Using Data Access Objects to perform any database interaction

Suppose we have an object called UserDAO which provides us with database access functions. In particular suppose it has a function readByUsername() which returns a single User object.

This data access object could be provided to the SecurityService.cfc via the init() function.

So our revised login code might look as follows:

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

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

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 login code might be modified as follows:

<cffunction name="login" output="false">
   <cfargument name="session">
   <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>
      <cfset session.userId = user.getUserId()>
   </cfif>
   <cfreturn isLoggedIn( arguments.session )>
</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.

What else should your Security Service do?

Your security service should provide any functions relating to usernames, passwords and authorisation. So you may want to include some additional functions such as

changePassword( session, oldPassword, newPassword ) – Changes the password if the old password matches the current password. Returns true if successful.

hasRole( session, role ) – Returns true if the user belongs to a specified role.

Plus any any other functions specific to the security of the system you are implementing.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
9 Jan 2007 01:36PM
Phillip Senn said:
Phillip Senn's Gravatar So why not have function login simply pass the query and let the calling program decide which columns it wants to use (provided the .RecordCount GT 0)?
9 Jan 2007 01:38PM
Peter Bell said:
Peter Bell's Gravatar Since you asked for comments on the list - here are some thoughts!

Firstly, I'd use ColdSpring (or LightWire when it's ready for primetime :->) to handle injecting this into any controllers that require it rather than just sticking it in application scope. Maybe not a big issue for this one component, but it'll become more important over time.

Secondly, it is fundamentally users that are being authenticated (at least in this case), so I'd give the responsibility for this to the UserService and then just delegate the responsibility to UserAuthenticationService - I think that'd be cleaner.

I'd also take a more OO as opposed to service level approach. A lot of the features you put into a service singleton I'd put into the a SiteUser.cfc business object that would probably be stored in session scope (assuming you were using sessions) - again delegating to a UserSecurity or UserAuthentication object as it's generally a good idea to put responsibilities into the business object rather than a service layer where possible.

Just some thoughts which I guarantee to be worth exactly what you paid for them :->
9 Jan 2007 02:38PM
Aaron Roberson said:
Aaron Roberson's Gravatar Where could I get an encryption component? I did not find anything on cfczone.org.
10 Jan 2007 01:19AM
Kevan Stannard's Gravatar Hi Phillip, the idea is to try and hide away how the authentication is actually performed, and we keep any database specific stuff (such as any references to table field names) inside your 'Data Access Objects' and nowhere else. If you did want to perform some kind of conditional login, then you could either have a couple of different login functions or pass an extra parameter in the login function. For example, you could have different function such as loginUsernamePassword(session,username,password) which authenticates as above, and loginMemberCode(session,code) which performs authentication by looking at a different field in the database.

Hi Peter, thanks very much for your comments. I have also copied some of your notes from CFCDev:

"I actually think some of those could go into a session based SiteUser object - I typically put operations into a service method only if they relate to a collection of objects or if an object instance doesn't yet exist (in which case the service method or the DI engine has to handle it - one of the two). Because that seems to me object specific, for know I put such code into the service method rather than a generalized factory. For example, I'd put changePassword() and hasRole() into the SiteUser business object. Not always the right approach, but not a bad generalized solution."

Would it make sense to have a reference to the security service stored inside the SiteUser object, and have a call on siteUser.chancePassword() (for example) delegated to the securityService.changePassword()?

Hi Aaron, for your encryption you could perhaps use the built in ColdFusion hash() function. I have not used this before, but it may be just what you need. Alternatively try a search for 'one way encryption coldfusion' and you should get some links. See the following link for some info on encryption and the hash() function: http://www.adobe.com/devnet/server_archive/article...
10 Jan 2007 04:53AM
Peter Bell said:
Peter Bell's Gravatar Hi Kevan,

I would put the changePassword() into the SiteUser, but I'd actually delegate to something other than the service layer. I think it is very easy to get into "OO Procedural coding" where we have class libraries (service classes) and fairly dumb TOs. Because of that, I try to limit service classes to handling collections of business objects or acting as a factory to return the business objects based on a query. Once I have a SiteUser, I'll either let it do the work, on in the case of authentication I'll compose it of SiteUserSecurity.cfc or something similar (still playing with exact naming and functions) to handle that rather than running everything through a service method.
10 Jan 2007 12:58PM
Kevan Stannard's Gravatar Just a couple of exta notes from CFCDev:

From Aaron:

"By putting the user in the session scope you would not have to pass in the session to any of the methods in your security class, right?

Example:
<cfset session.siteuser =
application.siteUser.getAuthenticatedUser(username,password) />

I kind of thought that Kevan's idea of passing in everything in the session scope was a little strange, but perhaps I am not getting it."

Yes, I think Aaron is right.

From Peter:

"Well, I'd probably do something more like:
<cfset session.SiteUser =
attributes.UserService.getAuthenticatedUser(Username,Password)>

This assumes you are in some kind of cfc that has has the UserService injected using ColdSpring or LightWire via constructor or setter injection."

Many thanks, Peter.
Add a comment
(will not be published)
(include http://)