Yesterday we looked at how to setup a login form and process the information with ColdBox. Today we'll go a step further by viewing the code behind the authentication model.

At the very beginning of our processLogin event we called upon the authenticationService object to, well, authenticate the user. The authentication model is made up of three distinct objects: authentication.cfc, authenticationService.cfc and authenticationGateway.cfc. The authenticaionService is the main entry for all requests to the model. The gateway is the part that interacts with the database. Authentication.cfc is basically just a bean that gets passed around alot.

authentication.cfc

Basically just a bean, it has a couple of properties and a series of get/set methods:

view plain print about
1<cfcomponent output="false">
2    
3    <cfproperty name="user_name" type="string" default="" />
4    <cfproperty name="password" type="string" default="" />
5    <cfproperty name="user_id" type="numeric" default="0" />
6    <cfproperty name="error" type="string" default="" />
7
8    <cffunction name="init" access="public" returntype="authentication">
9        <cfargument name="user_name" type="string" required="false" default="" />
10        <cfargument name="password" type="string" required="false" default="" />
11        <cfargument name="user_id" type="numeric" required="false" default="0" />
12        <cfargument name="error" type="string" required="false" default="" />
13        
14        <cfset setuser_name( arguments.user_name ) />
15        <cfset setpassword( arguments.password ) />
16        <cfset setuser_id( arguments.user_id ) />
17        <cfset seterror( arguments.error ) />
18        
19        <cfreturn this />
20    </cffunction>
21    
22    <cffunction name="setuser_name" access="public" returntype="void">
23        <cfargument name="user_name" type="string" required="true" />
24        <cfset variables.instance.user_name = arguments.user_name />
25    </cffunction>
26    <cffunction name="getuser_name" access="public" returntype="string">
27        <cfreturn variables.instance.user_name />
28    </cffunction>
29    <cffunction name="setpassword" access="public" returntype="void">
30        <cfargument name="password" type="string" required="true" />
31        <cfset variables.instance.password = arguments.password />
32    </cffunction>
33    <cffunction name="getpassword" access="public" returntype="string">
34        <cfreturn variables.instance.password />
35    </cffunction>
36    <cffunction name="setuser_id" access="public" returntype="void">
37        <cfargument name="user_id" type="numeric" required="true" />
38        <cfset variables.instance.user_id = arguments.user_id />
39    </cffunction>
40    <cffunction name="getuser_id" access="public" returntype="numeric">
41        <cfreturn variables.instance.user_id />
42    </cffunction>
43    <cffunction name="seterror" access="public" returntype="void">
44        <cfargument name="error" type="string" required="true" />
45        <cfset variables.instance.error = arguments.error />
46    </cffunction>
47    <cffunction name="geterror" access="public" returntype="string">
48        <cfreturn variables.instance.error />
49    </cffunction>
50
51</cfcomponent>

Nothing too special happening here. The setX methods store the value in the bean's instance so they can be retrieved later with the getX methods.

authenticationGateway.cfc

AuthenticaionGateway is the main entrance to our model. This is the object we request from ColdSpring in our processLogin event.

view plain print about
1<cfcomponent output="false">
2
3    <cffunction name="init" access="public" returntype="authenticationService">
4        <cfargument name="authenticationGateway" type="authenticationGateway" required="true" />
5        
6        <cfset variables.authenticationGateway = arguments.authenticationGateway />
7
8        <cfreturn this />
9    </cffunction>
10    
11    <cffunction name="authenticate" access="public" returntype="authentication">
12        <cfargument name="user_name" type="string" required="true" />
13        <cfargument name="password" type="string" required="true" />
14        
15        <cfset var loc = { } />
16        
17        <cfset loc.oAuthentication = createObject( "component", "authentication" ).init( argumentCollection=arguments ) />
18        <cfset variables.authenticationGateway.authenticateUser( authentication=loc.oAuthentication ) />
19    
20        <cfreturn loc.oAuthentication />
21    </cffunction>
22    
23    <cffunction name="isAuthenticated" access="public" returntype="boolean">
24        <cfargument name="authBean" type="authentication" required="true" />
25        <cfif arguments.authBean.getUser_id() AND NOT len( arguments.authBean.getError() )>
26            <cfreturn true />
27        <cfelse>
28            <cfreturn false />
29        </cfif>
30    </cffunction>
31
32</cfcomponent>

Our first method, init(), is going to be called by Coldspring to set the gateway automatically. The authenticate method is the one we call when we want to confirm that the entered user name and password are correct. The way we achieve this is by instantiating an authentication object and then passing it to the gateway for processing. We then return the processed bean to the requester.

The final method, isAuthenticated(), is a way to encapsulate the logic behind testing for successful authentication. The processLogin event doesn't need to know the rules behind a proper login. It just needs to ask the service by passing back the bean. The service knows that if the user_id isn't 0 and that there were no errors the authentication was a success.

authenticationGateway.cfc

This object is really simple and contains only two methods: init and authenticateUser.

view plain print about
1<cfcomponent output="false">
2
3    <cffunction name="init" access="public" returntype="authenticationGateway">
4        <cfargument name="dsn" type="string" required="true" />
5        <cfset variables.dsn = arguments.dsn />
6        <cfreturn this />
7    </cffunction>
8    
9    <cffunction name="authenticateUser" access="public" returntype="void">
10        <cfargument name="authentication" type="authentication" required="true" />
11        
12        <cfset var loc = { } />
13    
14        <cfquery name="loc.qAuthenticate" datasource="#variables.dsn#">
15        SELECT
16            name.user_id
17            , CASE WHEN pw.user_id IS NULL THEN 1 ELSE 0 END AS incorrect_password
18        FROM
19            users AS name
20        LEFT JOIN
21            users AS pw
22        ON
23            name.user_id = pw.user_id
24            AND pw.password = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hash( arguments.authentication.getPassword(), 'SHA-512' )#" />
25        WHERE
26            name.user_name = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.authentication.getUser_name()#" />;
27        </cfquery>
28        
29        <cfif loc.qAuthenticate.recordcount>
30            <cfif NOT loc.qAuthenticate.incorrect_password>
31                <cfset arguments.authentication.setUser_id( loc.qAuthenticate.user_id ) />
32            <cfelse>
33                <cfset arguments.authentication.setError( "INVALID_PASSWORD" ) />
34            </cfif>
35        <cfelse>
36            <cfset arguments.authentication.setError( "INVALID_USERNAME" ) />
37        </cfif>
38        
39    </cffunction>
40    
41</cfcomponent>

As before, the init method is called by Coldspring to properly initialize the object. We configured our coldspring.xml.cfm file to pass in the dsn parameter to the authenticationGateway object, which it does supernaturally. The authenticateUser method is where everything happens. Basically, it queries the database with the bean's user name and password properties.

I built my query this way so that with a single cfquery tag I could confirm the user name exists even if the password check fails. The first table (name) will have a record if the user name matches. If not, the entire resultset will be empty. The second table (pw) will only have a result if the password matches as well. The CASE clause checks for the existence of a record in the second table. If a row is found, it returns 1 (true). Otherwise, it returns 0 (false).

The second part of the method sets the user_id or the error, depending on the situation:

  • If there are results and
    • the password is correct: the user_id is set;
    • the password is incorrect: an error is set ("INVALID_PASSWORD");
  • If there are no results: an error is set ("INVALID_USERNAME");

Why go through all this?

You might be asking yourselves: why go to the trouble of doing all of this in the first place? Sure, I could've put that query right in the middle of my processLogin event and checked for the logic there. There are a couple of good answers to that (I hope!).

Encapsulation of logic

Best practice dictates that a controller of an MVC framework should do as less as possible. It's a traffic agent. It doesn't need to know how the cars run, it just has to know how to get them across the intersection without causing an accident. Getting our authentication logic out of the controller and into it's own model allows us to separate concerns and keep logic where it needs to be.

Reusability

What if our processLogin isn't the only one that will need to handle authentication? There might be other reasons to call upon the authentication logic: web services, different frontends (Flex, among others). If we have all of our login code in the controller we need to copy it to every event that needs to check for authenticaion. Our authenticaion code is also pretty generic. We could very easily copy it over to a different object, validate the query is still valid against the different database and we're set!

It ain't that complicated!

At first glance, it does look complicated. There are three .cfcs where a query and a couple of lines would've done the trick. However if you look closer you'll notice that there really isn't that much going on. Some generic set/get methods, another one for querying the database and two more for authenticating the user and making sure the authentication was a success. On top of that, the code within the controller is much simpler. All it needs to do is ask the authenticationService to do the heavy lifting.

I hope that clears it all up. One of the nice bonuses of our authentication model is that, although it relies on the users table, it isn't built from it. We couldn't use Illudium PU-36 Code Generator to generate our cfcs for us, we needed to write them on our own. I feel this helps to understand the relation between the different objects in our model. In most cases, however, our models are built from database tables so most of the code can be generated rather simply. All we need to do after is query them from our controllers and we're set!