Access Control in ColdFusion - The Basics (part 2) - Security Series #7.2

In part one of this access control sub-series we talk about assigning roles and user permissions to variables in the user's sessions. This allows you to access those variables programmatically from the users session while still not giving the user access to the variables for manipulation.

So our authentication code(post user validation), might look like this:


<cfset sValidation = application.userService.validateUser(form.username, form.password) />
<!--- Check to see if a valid user was returned --->
<cfif sValidation.userValid>

<cfset session.auth = StructNew() />

<cfset session.auth.isLoggedIn = true />

<!--- set user roles --->
<cfset session.auth.roles = sValidation.aRoles />
<cfset session.auth.permissions = sValidation.aPermissions />

<!--- if user was valid, reroute them to their myAccount page --->
<cflocation url="myaccount.cfm">
<cfelse>
<!--- If not valid, set error message --->
<cfset errorMessage = "Invalid Username or Password" />
</cfif>

Which might result in a session that looks like this:

So with this type of session, we could immediately start controlling access in a very simplistic manner simply by checking the session scope for values.

For example, if I wanted to control access to a link by providing it only to administrators I could:


<cfif IsDefined("session.auth.IsLoggedIn")
AND session.auth.IsLoggedIn
AND IsDefined("session.auth.roles.admin")>


<a href="http://admin.12robots.com/">Admin</a>
</cfif>

Of course, this could get ugly after a while, especially if a permission name changed, or it's position in the SESSION scope, or if we had 10 or more such assets on a page, or even on several pages, that needed this type of protection.

So, let's turn this in a centralized security function.


<cfcomponent output="false">
    
    <!--- Checks through users permissions to see if they have one of the permission passed in --->
    <cffunction name="checkRoles" access="public" returntype="boolean" output="false">
        <cfargument name="roleList" type="string" required="false" default="" />
        
        <cfset var i = "" />
        <cfset var keyName = "" />
        
        <!--- Check to see if the user is logged in, if not return false. --->
        <cfif NOT UserLoggedIn()>
            <cfreturn false />
        </cfif>
        
        <!--- Loop through the list that was passed in looking for a match --->
        <cfloop from="1" to="#ListLen(arguments.roleList)#" index="i">
            <cfset keyName = "session.auth.roles." & ListGetAt(arguments.roleList, i) />
            
            <!--- Once a permission match is found, the mthod returns true --->
            <cfif IsDefined(keyName)>
                <cfreturn true />
            </cfif>
        </cfloop>
        
        <!--- If no matches are found, the method returns false --->
        <cfreturn false />
        
    </cffunction>
    
    <!--- Check to see if user is logged in --->
    <cffunction name="UserLoggedIn" access="public" returntype="boolean" output="false">
        <cfif IsDefined("session.auth.IsLoggedIn") AND session.auth.IsLoggedIn>
            <cfreturn true />
        <cfelse>
            <cfreturn false />
        </cfif>
    </cffunction>
</cfcomponent>

Now, with the code above, we can change our statement from earlier to be:


<cfif application.security.checkRoles('admin')>
<a href="http://admin.12robots.com/">Admin</a>
</cfif>

And if we want to give another link to both Admins and Managers, we would use:


<cfif application.security.checkRoles('admin,manager')>
<a href="http://admin.12robots.com/">Managers</a>
</cfif>

This is of course, assuming that the security component that we wrote earlier was added to the application scope in your application initialization. So in your Application.cfc's onApplicationStart() method you would want something like:


<cfset application.security = CreateObject('component', 'cfc.security') />

Now, this is all very simplistic stuff, it can, and in many case should, get more complicated. Most would not like the idea of hard-coding permissions into a page, so some of your statements would look more like:


<cfif application.security.checkRoles(permissionListVariable)>
<a href="http://admin.12robots.com/">Managers</a>
</cfif>

The permission list for that link could then be queried from the Database. The lists of how you can handle this type of access control is endless.

I threw this simple function together this morning. I would love to hear constructive criticisms from the floor. I know it is very simple and I would be interested to hear if anyone feels like it could be easily circumvented, or if it could be improved without tearing apart the simplicity. These posting are designed to be an intro to those unfamiliar with security concepts and possibly new to CF.

Thanks for taking the time to read this far. As an FYI, I have now upgraded to BlogCFC 5.9.1 which allows for comment subscription without leaving a comment. Feel free to try it out.

Comments
Henry's Gravatar Why not use CF to the fullness, and use CFLOGIN tag instead?
# Posted By Henry | 7/15/08 11:51 AM
Jason Dean's Gravatar @Henry,

That is a good question. It is also one that I will eventually answer in another post. But for now I will say, that if all you are doing is Role-based security, then CFLOGIN is probably just fine. However, CFLOGIN is limited it its flexibility and its expandability. For a truly customizable experience, you really need to roll your own security.

That said, I have nothing against CFLOGIN, for some it is likely a perfect solution. I used to be a CFLOGIN evangelist until I came across user-level permission issues.

Perhaps I am misunderstanding CFLOGIN's capabilities, but I would think for most larger applications, especially if they require user-level permissions that CFLOGIN is inadequate.

Any one else?
# Posted By Jason Dean | 7/15/08 12:08 PM
Sami Hoda's Gravatar We have a massive app, with intricate permissions, that uses CFLogin. Despites its known and published flaws, it works for us.
# Posted By Sami Hoda | 7/15/08 12:26 PM
Steve Withington's Gravatar @Jason
Could you expand your comment a little more regarding your issues with cflogin and user-level permission?

I've used something similar to the following in the past to address user-levels myself, but maybe I'm misunderstanding you:

<cfif IsUserLoggedIn() and IsUserInRole('admin')>
<cfinclude template='admin.cfm' />
<cfabort />
</cfif>
# Posted By Steve Withington | 7/15/08 12:42 PM
Jason Dean's Gravatar @Sami - Thanks for the info. Like I said, i may be missing something about CFLOGIN

@Steve - What I mean was that all privileges are granted through roles in CFLOGIN. I cannot give a privilege to a user without assigning them to a role. In some cases this may not be an issue, but I had one project where the client wanted to be able to assign any one of 60-80 privileges to individual users. Now I suppose I could have made 60-80 groups and then just assigned the users to the groups as needed, but that seemed hackish.
# Posted By Jason Dean | 7/15/08 12:59 PM
Mike Rankin's Gravatar I don't think the 80 roles would present a terrible challenge to the cflogin structure. You're still going to have to come up with an interface that lets you manage that one way or another. Where things tend to get hairy is where you want to attach specific rights to assets; ie. I have read/write rights on this document and read only on that document. Trying to figure out how to allow your users to assign rights to their own assets is challenging.
# Posted By Mike Rankin | 7/15/08 1:18 PM
Jason Dean's Gravatar @mike I am sure it could have handled it just fine. But again, it seemed hackish, to me, to use roles as permissions in this type of situation.

Like I have said, more many people, in many situations, CFLOGIN may be just fine. Even for large Apps as Sami said. But it can't cover everything.

I will throw out that you could certainly use CFLOGIN as a Base Authorization/Authentication system and supplement it will your own security components and functions. I think I may look into that for a future post. I would probably still use my security singleton for the security API and have the methods within call the CFLOGIN methods. I will definitely need to look into that.
# Posted By Jason Dean | 7/15/08 1:51 PM
Steve Withington's Gravatar @Jason,
Ok. Wow though ... 60-80 groups? Have fun with that.

For me, I allow a user to have more than one role, where each role has defined 'permissions' so if a user has both 'HR' and 'Marketing' roles for example, they would inherit permissions for both. I guess this might be where some of us (and clients) ... Roles vs. Permissions, OR Roles with defined permissions.
# Posted By Steve Withington | 7/15/08 2:02 PM
Oliver's Gravatar Hi Jason,

I'm new to CF 8 and have been eagerly reading your articles on access control. However to my dismay I'm getting a bit lost with where everything needs to go.
Maybe its just me, but I'm not clear as to which files contain what information and function.

I'm not sure what goes in the Application.cfc and how you go about controlling the login process and the assignment of permissions for a user.

Again apologies as this may seem fairly straight forward but I'm new to the concepts in CF 8 and whilst I can do most of what you have explained in CF 5 I'm just not getting it without seeing a full working example.

Having said that, do you by any chance have a working downloadable example of your security series that covers all the concepts you have discussed?

I'm very keen to get a login authentication and permission setting app working but don't really know how to structure it in its entirity.

Any help would be appreciated.

Many thanks in advance.
O
# Posted By Oliver | 10/19/08 5:57 AM
Jason Dean's Gravatar @oliver - Thanks for the question. I wish I had a better answer for you.

As with some many things is programming, there are so many ways to accomplish the same tasks, there is no one "right way" to set up any system.

Also a lot depends upon if you are writing a procedural, OO, or mixed application and about how much you care about following conventions or ensuring portability of the app.

That said, I will try to answer briefly where you MIGHT put some of these things.

The code for validating the user and adding the arrays of permissions would either go into the login action page or into a function that is called after a login attempt.

The struct itself is contained in the session scope and can be access that way.

Checks to see if a user is in a role can be anywhere. So if you want to make sure a user is in the role "admin" before you display a certain view, You can put that code in the view.

While you are learning, I would say put the code where it:
1. Works
2. Makes sense to you.

Once you get it working in a way that makes sense to you, have it looked at by a more experienced developer and ask for suggestions. This is a great way to learn.
# Posted By Jason Dean | 10/19/08 9:33 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner