My Approach to Basic Security in a Model-Glue Application
By now almost all of us have "rolled our own" when it comes to giving a Coldfusion app some semblance of security, so the concept is nothing new. It can be, however, somewhat daunting when attempting to take what we know procedurally and apply it to an MVC styled application.
The Kansas City Coldfusion User Group invited me to assist them with a group project using Model-Glue, specifically asking me to show them how to implement security. What follows is my way of thinking about and approaching the implementation of basic security in a Model-Glue application. At the end of this post is a link to download the tutorial app I put together for them as well since I know "a line of code replaces a thousand lines of abstract explanations".
Okay, so how do I accomplish the above laundry list? Model-Glue comes pre-configured with a generic controller that has methods and listeners already set up to perform whatever action you want at the following times:
This is the generic controller I was referring to, and the OnRequestStart listener and method is the one we'll be leveraging. Since OnRequestStart is called before the event itself is executed, it is the perfect place to do ensure that our default values are present and available to the rest of the app at all times.
Here is the onRequestStart method after I modified it to accommodate security:
Here's what's happening.:
Before any event is executed, I am first checking for the existence of a "user" key in session. If it’s not there, I’m creating an empty instance of the user.cfc in my model (which was auto-wired in to this controller…you can see how that's done by checking out the sample app at the end of this post) and putting it into session. I’m then checking for the existence of a "loggedin" key in session, and if not there, creating it with a value of false. After this, I put both session values into the Event bucket and let the event that was called finish its execution.
The Process:
Also, just for disclaimer purposes, I didn't invest a lot of time making it overly pretty, nor did I comment it as heavily as I normally would. I do believe, however, that it's a very good jumping off point for someone who has been wondering "how to think" about security in an MVC app.
Hope you find it useful!
Doug out.
DOWNLOAD SECURITY SKELETON APP ZIP
The Kansas City Coldfusion User Group invited me to assist them with a group project using Model-Glue, specifically asking me to show them how to implement security. What follows is my way of thinking about and approaching the implementation of basic security in a Model-Glue application. At the end of this post is a link to download the tutorial app I put together for them as well since I know "a line of code replaces a thousand lines of abstract explanations".

GOALS OF SECURITY
What we’re wanting to accomplish is this:- User arrives at our app.
- We evaluate their current login status, setting the appropriate values in the appropriate places so that those portions of our app who DO care if the user is logged in or not can make the right choices.
- Those values follow the user along their tour of our app.
PRE-REQUEST ACTIONS
Putting this scenario under the MG microscope then, and following along the path of the MG event lifecycle, here’s what I want to make happen:- User arrives, an event is called (either the default event if none is specified or the one explicitly called);
- Before that event is processed, I ensure that I have in session for this person:
- a “loggedin” variable with either a value of true or false;
- a user object, whether it’s empty or populated.
- Make those session values available to the rest of my app by placing them in the Event bucket
Okay, so how do I accomplish the above laundry list? Model-Glue comes pre-configured with a generic controller that has methods and listeners already set up to perform whatever action you want at the following times:
- onRequestStart
- onRequestEnd
- onQueueComplete
<controllers>
<controller name="GlobalController" type="controller.Controller">
<message-listener message="OnRequestStart" function="OnRequestStart" />
<message-listener message="OnQueueComplete" function="OnQueueComplete" />
<message-listener message="OnRequestEnd" function="OnRequestEnd" />
</controller>
</controllers>
<controller name="GlobalController" type="controller.Controller">
<message-listener message="OnRequestStart" function="OnRequestStart" />
<message-listener message="OnQueueComplete" function="OnQueueComplete" />
<message-listener message="OnRequestEnd" function="OnRequestEnd" />
</controller>
</controllers>
This is the generic controller I was referring to, and the OnRequestStart listener and method is the one we'll be leveraging. Since OnRequestStart is called before the event itself is executed, it is the perfect place to do ensure that our default values are present and available to the rest of the app at all times.
Here is the onRequestStart method after I modified it to accommodate security:
<cffunction name="onRequestStart" access="public" returnType="void" output="false">
<cfargument name="event" type="any">
<cfif not structkeyexists(session,"user")>
<cfset session.user = getUser().init() />
</cfif>
<cfif not structkeyexists(session,"loggedin")>
<cfset session.loggedin = "false" />
</cfif>
<cfset arguments.event.setValue("loggedin",session.loggedin) />
<cfset arguments.event.setValue("user",session.user) />
</cffunction>
<cfargument name="event" type="any">
<cfif not structkeyexists(session,"user")>
<cfset session.user = getUser().init() />
</cfif>
<cfif not structkeyexists(session,"loggedin")>
<cfset session.loggedin = "false" />
</cfif>
<cfset arguments.event.setValue("loggedin",session.loggedin) />
<cfset arguments.event.setValue("user",session.user) />
</cffunction>
Here's what's happening.:
Before any event is executed, I am first checking for the existence of a "user" key in session. If it’s not there, I’m creating an empty instance of the user.cfc in my model (which was auto-wired in to this controller…you can see how that's done by checking out the sample app at the end of this post) and putting it into session. I’m then checking for the existence of a "loggedin" key in session, and if not there, creating it with a value of false. After this, I put both session values into the Event bucket and let the event that was called finish its execution.
THE LOGIN PROCESS
Okay, so now we have appropriate values present and available throughout the app. How about the actual logging in process? Let’s walk through it and see how the different parts are arranged. It’s pretty simple (and mostly familiar), you’ll see.The Process:
- User is presented with a login form whose action calls the “login” event;
- The login event makes a broadcast to the authentication controller to perform it’s “login” method, username and password being present in the Event bucket since they were submitted via the login form;
- The Authentication controller creates an empty instance of the user object, then calls the user object’s ‘login’ method, passing in the username and password arguments;
- Internally to the User object, IF the user was found based on the credentials passed in, it populates its internal variables (firstname, lastname, id, etc.);
- The authentication controller checks to see if the User object’s ID value is anything other than the default value of zero. If it IS, then authentication was successful. If it’s zero, authentication failed.
- If authentication was successful, we put that instance of the user object into session, set session.loggedin equal to true, put both the user object and the loggedin value into the Event bucket, and add a Result value of “success” so that our event will know what action to take.
- If authentication failed, we put a message into the Event bucket indicating this (“login failed! Try again!”), and add a Result value of “failure”, again so that our event will know where to redirect to.
<cffunction access="public" name="login" output="false" returntype="void" hint="I make the call to log a user in">
<CFARGUMENT name="event" type="any">
<cfset var objUser = getUser().init() />
<cfset objUser.login(username = arguments.event.getValue("username"), password = arguments.event.getValue("password")) />
<cfif objUser.getID() IS NOT 0>
<cfset session.loggedin = true />
<cfset session.user = objUser />
<cfset arguments.event.setvalue("loggedin",true) />
<cfset arguments.event.addResult("success") />
<cfelse>
<cfset arguments.event.setvalue("loggedin",false) />
<cfset arguments.event.setvalue("message","Login failed! Try again.") />
<cfset arguments.event.addResult("failure") />
</cfif>
</cffunction>
<CFARGUMENT name="event" type="any">
<cfset var objUser = getUser().init() />
<cfset objUser.login(username = arguments.event.getValue("username"), password = arguments.event.getValue("password")) />
<cfif objUser.getID() IS NOT 0>
<cfset session.loggedin = true />
<cfset session.user = objUser />
<cfset arguments.event.setvalue("loggedin",true) />
<cfset arguments.event.addResult("success") />
<cfelse>
<cfset arguments.event.setvalue("loggedin",false) />
<cfset arguments.event.setvalue("message","Login failed! Try again.") />
<cfset arguments.event.addResult("failure") />
</cfif>
</cffunction>
SUMMARY
Those are the items of interest to understand and think about when doing basic authentication. Now, since I know that reading about a thing abstractly is not NEARLY the same as chewing on actual code, I zipped up the whole security skeleton app so you can download it and poke through its innards. It has nothing in it except for the basic security outlined above, but it DOES function (I'm using Querysim in my model).Also, just for disclaimer purposes, I didn't invest a lot of time making it overly pretty, nor did I comment it as heavily as I normally would. I do believe, however, that it's a very good jumping off point for someone who has been wondering "how to think" about security in an MVC app.
Hope you find it useful!
Doug out.
DOWNLOAD SECURITY SKELETON APP ZIP
Subscription Options
You are not logged in, so your subscription status for this entry is unknown. You can login or register here.
Re: My Approach to Basic Security in a Model-Glue Application
Why bother copying the session variables into the event scope? Why not just keep them in session?
Posted by jeff on September 26, 2007 at 12:00 AM
Re: My Approach to Basic Security in a Model-Glue Application
Outstanding question Jeff. The answer is, because I need them available to my views, and views may NOT talk directly to session. The Event object is the common denominator between the M the V and the C, so I take them from session and put them into the Event bucket to ensure they are available for whatever part of my app needs them.
Posted by dougboude on September 26, 2007 at 12:10 AM
Re: My Approach to Basic Security in a Model-Glue Application
This is a very interesting post.
I found it while researching a bit for a project I have, maybe you can share you take on this.
* I have an application where there are multiple Roles, Basic user, Power Users and Admins.
* All can access pretty much the same pages but they are supposed to see/do different things on the page.
* An example is this : a simple Details Form.
* Basic Users can see everything on the form but cannot edit all the fields, just their names. All other fields are supposed to be read only.
* Power users land on the same page and they can pretty much update anything on the the form but they are not allowed to even see the 'Delete' button at the end of the page.
* Admins can create new data and can then come back to the form and update/delete anything they want. They can actually see the 'Delete' Button which deletes the whole record and enter notes on a TextArea only available to them.
* 6 months from now a new Role with a different set of access rules gets created.
How would you approach this? It is not a matter of some users see something and some see something else. All of them see basically the same stuff but with different levels of access Read Only, Edit, Edit/Delete, etc.
I'll play with the code you provided for this post and maybe I'll get a couple more ideas. In the meantime I'd be really interested in how you would do it.
I'm a beginner on ModelGLue and I'm finding a bunch of your posts really useful. Please keep up with the postings, they are a good resource.
Fernando
I found it while researching a bit for a project I have, maybe you can share you take on this.
* I have an application where there are multiple Roles, Basic user, Power Users and Admins.
* All can access pretty much the same pages but they are supposed to see/do different things on the page.
* An example is this : a simple Details Form.
* Basic Users can see everything on the form but cannot edit all the fields, just their names. All other fields are supposed to be read only.
* Power users land on the same page and they can pretty much update anything on the the form but they are not allowed to even see the 'Delete' button at the end of the page.
* Admins can create new data and can then come back to the form and update/delete anything they want. They can actually see the 'Delete' Button which deletes the whole record and enter notes on a TextArea only available to them.
* 6 months from now a new Role with a different set of access rules gets created.
How would you approach this? It is not a matter of some users see something and some see something else. All of them see basically the same stuff but with different levels of access Read Only, Edit, Edit/Delete, etc.
I'll play with the code you provided for this post and maybe I'll get a couple more ideas. In the meantime I'd be really interested in how you would do it.
I'm a beginner on ModelGLue and I'm finding a bunch of your posts really useful. Please keep up with the postings, they are a good resource.
Fernando
Posted by fernando lopez on November 3, 2007 at 12:55 AM
Re: My Approach to Basic Security in a Model-Glue Application
Fernando,
I would add on to my user object the ability to flag (preferably by Role, not by User): canEdit (bit), canAddDelete (bit). Now when I get my session back (session.user = objUser), I should have access to those bit settings. Adding to Doug's code above, if most of your app needs to see these security settings, then we can add them directly to the Event:
cfset arguments.event.setvalue("canEdit", objUser.getCanEdit())
cfset arguments.event.setvalue("canAddDelete", objUser.getCanAddDelete())
Now, those values are exposed to the views as well as the controllers, so in the view you can do this type of thing:
cfif viewState.getValue("canEdit")
-- show INPUT field --
cfelse
-- show raw data only, and maybe a HIDDEN input field for form handling --
For the delete button:
cfif viewState.getValue("canAddDelete")
-- show DELETE button --
cfelse
-- don't render DELETE button --
For maximum security, you can do the same tests in the objects, since they get arguments.Event in all cases: you can test in the Delete function whether the current user is even allowed to execute that command, say if he processed it through a hand-written URL to get around your hidden DELETE button.
HTH,
Jason
I would add on to my user object the ability to flag (preferably by Role, not by User): canEdit (bit), canAddDelete (bit). Now when I get my session back (session.user = objUser), I should have access to those bit settings. Adding to Doug's code above, if most of your app needs to see these security settings, then we can add them directly to the Event:
cfset arguments.event.setvalue("canEdit", objUser.getCanEdit())
cfset arguments.event.setvalue("canAddDelete", objUser.getCanAddDelete())
Now, those values are exposed to the views as well as the controllers, so in the view you can do this type of thing:
cfif viewState.getValue("canEdit")
-- show INPUT field --
cfelse
-- show raw data only, and maybe a HIDDEN input field for form handling --
For the delete button:
cfif viewState.getValue("canAddDelete")
-- show DELETE button --
cfelse
-- don't render DELETE button --
For maximum security, you can do the same tests in the objects, since they get arguments.Event in all cases: you can test in the Delete function whether the current user is even allowed to execute that command, say if he processed it through a hand-written URL to get around your hidden DELETE button.
HTH,
Jason
Posted by Jason on November 7, 2007 at 12:56 PM
Re: My Approach to Basic Security in a Model-Glue Application
Jason, That gives me a starting point on how to do things under Model-Glue.
As I said I'm still trying to learn the framework. While I do that our current applications need to keep working. At some point will move to a completely ModelGlue app, for now I'm fixing things under our old way of coding **read : spaghetti**.
I found that having users jumping directly to a page and bypassing the rendered button that triggers the action can be a serious problem. I do Template Level checks and validate the users rights to even access the template to delete.
I'll be posting something on my page once I'm done with our solution.
I'll keep in mind your advice once we start moving to MG and have to replicate what I'm developing right now, thanks for the pointers.
Fernando
@Doug : I try to login using http://www.dougboude.com/blog/login.cfm and I keep getting errors.
As I said I'm still trying to learn the framework. While I do that our current applications need to keep working. At some point will move to a completely ModelGlue app, for now I'm fixing things under our old way of coding **read : spaghetti**.
I found that having users jumping directly to a page and bypassing the rendered button that triggers the action can be a serious problem. I do Template Level checks and validate the users rights to even access the template to delete.
I'll be posting something on my page once I'm done with our solution.
I'll keep in mind your advice once we start moving to MG and have to replicate what I'm developing right now, thanks for the pointers.
Fernando
@Doug : I try to login using http://www.dougboude.com/blog/login.cfm and I keep getting errors.
Posted by grtfercho on November 8, 2007 at 11:16 AM
Re: My Approach to Basic Security in a Model-Glue Application
So there's no need for a password in this example? Please pardon my noobieness, but I'm trying to get a handle on MG and came across your example. I see the password argument is required, but I seem to be able to log in without it.
Posted by Jon on January 3, 2008 at 11:13 AM
Re: My Approach to Basic Security in a Model-Glue Application
Thanks Doug for sharing this great example. I all ready apply it to one of my projects and your share makes it look greater and pro.
I have one question, I hope you can help me with: My app has a time out session of 30 minutes, and some parts of the site I use CFDIV and ColdFusion.navitate, etc. I foud out that if I leave the page open "inside" the Admin session if I click a "hard" link I get redirect to my login page, but if I click on an ajaxlink the content gets loaded and possible be edited. I was thinking how can I trigger some function when timeout session to cancel any possible processes if the page is leaved open inside an Admin session.
Thanks again.
I have one question, I hope you can help me with: My app has a time out session of 30 minutes, and some parts of the site I use CFDIV and ColdFusion.navitate, etc. I foud out that if I leave the page open "inside" the Admin session if I click a "hard" link I get redirect to my login page, but if I click on an ajaxlink the content gets loaded and possible be edited. I was thinking how can I trigger some function when timeout session to cancel any possible processes if the page is leaved open inside an Admin session.
Thanks again.
Posted by Felipe Serrano on April 18, 2008 at 1:35 AM