Welcome!

Adobe Flex Authors: Liz McMillan, RealWire News Distribution, Maureen O'Gara, Yakov Fain, Keith Swenson

Related Topics: Adobe Flex

Adobe Flex: Article

Customized ColdFusion

Customized ColdFusion

Three years ago, I wrote an article in ColdFusion Developers Journal discussing how to create customized roles-based Coldfusion {CF} authentication {CFDJ Vol. 2 Issue 3: "Customize ColdFusion Authentication 1"} The article focused on showing how to implement page-level security within CF without the pains of setting up advanced security in ColdFusion 4.5.1.

Do you remember <CFAUTHENTICATE>, isAuthorized(), and isProtected(), and how hard it was to properly configure a userDirectory? It didn't even work with certificates! Well, since Macromedia completely removed Netegrity's SiteMinder and replaced it with the JAAS (Java Authorization and Authentication Service) in ColdFusion MX (CFMX), I figured it's time to do an update.

If you followed the example in the first article, or if you just happened to already roll your own security paradigm in CF, you were in good shape for the security changes in CFMX. This is because you already wrote your own code to perform user authentication against some back-end user database (LDAP, Active Directory, RDBMS, etc.) and assigned user roles based on group memberships within the user database. (Code examples for this article can be downloaded from www.sys-con.com/mx/sourcec/cfm).

Additionally, you should have already coded a mechanism for authorizing logged-in user access to features and functionality within your applications. If you were diligent, you even included logic to ensure your authentication code only ran once per user session. CFMX provides this framework for you with the CFLOGIN scope and ColdFusion Components (CFCs).

Keeping in line with the first article, the scope of this article also focuses on user authentication and access at the page level within a given CFMX application. Naturally, this begs questions about other security concerns such as protecting CF templates with OS-level permissions and integrating CFMX with Web server access controls (e.g., digest security). These are all beyond the scope of this article but if there's interest, I will cover these and other topics in future articles. Again, this article will focus on the basics of using the CFLOGIN scope to authenticate and authorize users via CFCs.

User Security Basics
Let's talk basics. Authentication is the process of identifying users - that they are who they say they are. This is typically performed with a username/password challenge, but can be as seamless as using X.509 certificates, or as conclusive as biometric devices. When answering the challenge, a query validates the user's input against some form of user store: RDBMS, LDAP, Active Directory, etc. Authorization is the process of identifying the rights and permissions of the authenticated user. The user store is also the deposit for these rights or group memberships.

CFMX provides new tags and functions to control user security. <CFLOGIN> provides a container for performing user authentication and authorization and instantiates the CFLOGIN structure. Code inside the CFLOGIN tags executes only for unauthenticated users. The CFLOGIN structure contains two variables: CFLOGIN.name and CFLOGIN.password. Populate these variables with one of the following:

  • j_username and j_password form fields
  • username and password values (or hashes) passed in the Authorization header in Web Server Authentication (Basic, NTLM, Digest, etc.)
  • setCredentials method of a Flash Remoting Call
<CFLOGINUSER> identifies (or logs in) the authenticated user to CFMX. It requires three parameters: name, password, and roles. Typically, you pass the CFLOGIN.name and CFLOGIN.password values to CFLOGINUSER. The roles attribute is a comma-separated list of authorized application roles. The getAuthUser function retrieves the CFLOGINUSER name attribute; the isUserInRole function confirms whether the user is a member of the specified role. Finally, <CFLOGOUT> logs the user out of the CFMX application, and destroys all traces of the user's ID, password, and roles.

In order to implement page-level security in CFMX, we need to authenticate the user, figure out the user's permissions, and then check those permissions on protected pages/sections of the application. We will use a simple login form to present the authorization challenge (see Code I). We will code a CFC to handle the authentication and authorization, instantiate the CFC inside the CFLOGIN section of the Application.cfm, and store the authenticated user's group memberships (or access levels) within a session-level structure.

The Auth CFC
The heart of our security paradigm is the CFC, so let's start there. As I said, authentication and authorization happen against some sort of user directory. You pick your poison: LDAP, Active Directory, RDBMS, NT SAM, etc. To continue in the spirit of the first article I will use an LDAP as my user directory. However, this time I will be using a username/password combination instead of X.509 certificates.

The Auth CFC will have four methods: init, authenticate, authorize, and setAuthUser. The init method is a "default" constructor that returns an instance of the CFC. It sets some default LDAP connection values for use by other methods in the CFC. You will also notice the <cfset init()> in the pseudo constructor area - the area after the opening <CFCOMPONENT> and before the first <CFFUNCTION>. Since the code in this area automatically runs upon CFC instantiation, the Init function will fire even without invocation (see Code II).

For more information on using the init method as a constructor, see Rob Brooks-Bilson's "Top Ten Tips for Developing ColdFusion Components" at www.oreillynet.com/pub/a/javascript/2003/09/24/coldfusion_tips.html.

Next is the authenticate method. This method requires two arguments: username and password. Pass these arguments to the Username and Password <CFLDAP> attributes, providing authentication to the LDAP. Upon successful authentication (i.e., a valid username/password pair in the LDAP), the method returns true; otherwise it returns false. The Boolean values allow for efficient CFIF evaluations, such as <CFIF auth.authenticate(#Form.j_username#, #FORM.j_password#)>. (See Code III.)

The authorize method also requires the username and password arguments. Because the username and password are passed directly to the LDAP, the authorize method also provides a means of authentication. However, this method returns a comma-separated list of groups to which the user belongs (see Code IV). Within your code - or even at the Web server level - you can create access control lists (ACLs) to assign permissions to your LDAP groups. The application/system rights and permissions assigned to the groups extend to its members. These group memberships are roles within the CFMX security paradigm.

We now have simple methods for authentication and authorization within our application. However, it is not very efficient to authenticate the user without retrieving any of his or her attributes from the LDAP; nor is it efficient to make two separate CFC calls to build a user object. The setAuthUser method provides this functionality. The LDAP query in setAuthUser provides the same authentication as the authenticate method, but it also retrieves useful user attributes and stores them in a local structure. Next setAuthUser calls the authorize method to retrieve the user's group memberships, and then adds the returned list to the local user structure. Finally, setAuthUser returns the user structure (see Code V).

Notice that I wrap the body of the methods in Try-Catch blocks. This standard error-trapping technique provides valuable debugging information. <CFLDAP> now retrieves the actual error returned by the LDAP server instead of throwing a generic error message. Log the errors to a customized log file with <CFLOG>. The <CFTHROW> enables all <CFCATCH> in calling templates to display the LDAP error caught in the CFC method.

The Login Structure
Let's now turn our attention to the Application.cfm (see Code VI). First, create the CFMX Application framework with a <CFAPPLICATION> tag. Enable SESSIONMANAGEMENT and a SESSIONTIMEOUT (or use the default timeout specified in the CFMX Administrator). CFMX 6.1 adds the LOGINSTORAGE attribute to <CFAPPLICATION>, which specifies whether to store the authentication information in a non-persistent cookie (default) or the Session scope. Use the default LOGINSTORAGE=Cookie for this example. Next, code some conditional logic to control user logout. You can do this nicely by wrapping <CFLOGOUT> in a <CFIF> block that checks for a URL or FORM variable. You can strengthen the logout by explicitly clearing the SESSION scope.

The <CFLOGIN>...</CFLOGIN> provides the security container for processing the authentication code for every unauthenticated user request. Performing this authentication only once during the user's session is optimal. The conditional logic forces the user to the login form if the CFLOGIN scope is not instantiated. The j_username and j_password submitted from the loginform.cfm instantiate the CFLOGIN scope.

Begin the authentication process by instantiating the auth.cfc into a shared scope. The Application scope makes the most sense because you want the CFC to persist across user sessions. Use <CFOBJECT> or createObject() to instantiate the CFC instead of <CFINVOKE>. <CFINVOKE> transiently invokes the CFC but will not persist the instance. Call the setAuthUser method to authenticate the user, retrieve the user's group memberships, and build an object containing the user's memberships and other LDAP attributes. Return this object into a Session-level container for the current user.

If setAuthUser() is successful, the Session.User variable will contain the authenticated object structure, providing a simple user profile structure that can be used throughout the site. Now we can log the user into CFMX with <CFLOG INUSER>. Use the CFLOGIN.name and CFLOGIN.password variables for the username and password attribute values. Pass the Session.User.Group value to the CFLOGINUSER to authorize the user for those roles. Then set a Session-level variable that indicates the login status.

The final construct in the CFLOGIN container is a CFCATCH block that handles any errors in the login process. It is important to wrap all of the security code in the Try-CATCH syntax for error control. The code in this block sets the login status indicator to false, creates a login failure message, and returns the user to the login form. The CFCATCH.Message will return any error messages thrown from the CFC methods.

User Authorization
Now that CFMX has authenticated the user, and his/her profile information and group memberships are stored in a Session-level object, use that information to provide authorization throughout the application. Use getAuthUser() to retrieve the username of the logged-in user. Implement access control within your templates by wrapping objects, code, or even the entire template itself in an IF-Else block that uses isUserInRole() to authorize the logged-in user for the protected functionality. The index.cfm provides an example of retrieving the logged in username, displaying the user profile, and using roles-based authorization to display links and/or text (see Code VII).

Conclusion
Leveraging ColdFusion Components and the security tags and functions in ColdFusion MX provides a robust system for roles-based authentication. Persistent CFCs provide a layer of abstraction and code reuse. The CFLOGIN container runs security code for any unauthenticated user request, ensuring that the challenge and authentication of the user happens only once. The built-in functions allow granular page-level access control. These features can augment any existing custom security logic for your CFMX applications.

Notes

  • CFLOGIN LoginStorage attribute: At the time of this writing, the CFLOGIN LoginStorage attribute contains a bug (53320) when specifying SESSION. This bug does not completely clear the internal security scope when logging out of CFMX via <CFLOGOUT>. Although the session data is tied to the logged-in user, the bug results in a cached user login exposable to the next user login. This bug does not exist when using cookies to store the authentication information.

    CFMX uses a base64-encoded string containing the application name, username, and password as the in-memory cookie value (CFAUTHORIZATION_applicationname) sent with every page request. You may want to consider using SSL and domain-level cookies to protect this information, or utilizing the Session scope to persist this authentication information. You can read more about the LOGINSTORAGE attribute in the CFMX LiveDocs at http://livedocs.macromedia.com/coldfusion/6.1/htmldocs/appsec10.htm.

  • CFC Instantiation versus invocation: The code accompanying this article uses createObject() to instantiate the Auth CFC (auth.cfc). It is common practice for developers to use multiple CFINVOKE tags to invoke CFC methods. Accessing CFCs in this manner does not create a persistent instance of the CFC, and therefore does not run the instance data in the CFC pseudo-constructor. Although this constructor code is not required to execute the CFC, it is a best practice to utilize this area. This is why I advocate the use of <CFINVOKE> for method invocation of persistent CFCs instantiated with createObject() and <CFOBJECT>. Consult the CFMX LiveDocs for more details of CFC instantiation and invocation at http://livedocs.macromedia.com/coldfusion/6.1/htmldocs/buildi10.htm.
  • Access control: Most Web servers provide site and directory-level access control, requiring user authentication to access folders or directories containing the Web site files. I am not privy to native methods of extending this functionality beyond the template and/or page level to the elements within the code (e.g., links, text, variables, etc). This custom design does extend the Web server's own access control to the element level via CF, utilizing the same user database for authentication.

More Stories By Sheldon Sargent

Sarge is Sr. Technical Support Engineer, Adobe Systems, based in Gilbert, Arizona. He has been coding advanced applications in CF since version 2.0.

Comments (4) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
Bobby 11/07/07 08:46:25 AM EST

I can't find the code examples, the link given in the article gives a 404 error. Can anyone point me to the examples?

Nathan Mische 03/19/04 09:54:13 PM EST

Where can I find more info on the CFLOGIN LoginStorage attribute bug (53320)?

Bill Lewington 03/17/04 03:00:33 AM EST

I feel LDAP is a better way to go, so I disagree with Jack. With Microsoft''s Active Directory using LDAP being a little mor standard that SAM, it has made my life easier with authentication on a big Intranet site.

Jack Ince 03/13/04 12:28:22 PM EST

Sarge, I liked your article and it was benificial. However I do not know why you would add the added complexity of LDAP when most CF people use SQL or Access. To try and use your code I have to create a LDAP directory and that will have to be another adventure as I have never looked into all the espects of LDAP for a private site. Yhanls for your insight into this important subject.