Code
You can implement security in the following ways.
Using the Dreamweaver Login Wizard
ColdFusion installs a Login Wizard command in the Dreamweaver Commands menu that generates a skeleton set of pages for managing user authentication and authorization.
The wizard asks you to select how to authenticate the login information. Select one of the following options:
- Simple Specify a single user ID and password in the wizard. All users must enter this information to log in. Use this option for testing, or use the generated files as a template where you can replace the authentication code with more complex code. For example, to verify the ID and password against a database.
- NT domain Specify an NT domain in the wizard, and the wizard generates code that queries the domain.
- LDAP Specify the LDAP server and port, the user name and password required to access the login data, and the distinguished name to use to start the search for the user name. The wizard generates the code to query the LDAP server with the user ID and password.
The wizard asks you to select one of the following options for displaying the request for login information:
- Browser Dialog Box
- ColdFusion Login Form
Structure code generated by the Login Wizard
The wizard generates or modifies the following files in the directory or site that you specify:
Application. cfc If this file does not exist, the wizard creates it with a single onRequestStart method; it does not specify an application name or any other methods. If the file exists, but does not have an onRequestStart method, it adds the method. If Application.cfc and the onRequestStart method exist, the wizard inserts the required code at the beginning of the method. The resulting onRequestStart method has a cfincludetag that specifies mm_wizard_application_include.cfm; it also has a simple form with a logout button, which appears at the top of each page in the application.
If the wizard creates the Application.cfc file, change the file to specify the application name. For more information on Application.cfc, see Designing and Optimizing a ColdFusion Application.
- mm_wizard_application_include. cfm The Login Wizard uses the information specified in the wizard fields to set several CFC method arguments. It then uses them to invoke the performlogin method of the master login CFC, mm_wizard.authenticate.
- mm_wizard_authenticate. cfc This CFC contains all of the user authentication and login logic. The CFC consists of the following methods:
- The ntauth , ldapauth , and simpleauth authentication methods check the user's name and ID against the valid login information, and return information about whether the user is authenticated. For the details of how they authenticate the user and the specific return values, see the methods.
- The performLogin method is the master login method. It contains the cflogin tag, which displays the login form and calls the required authentication method. If the authentication method's return argument indicates a valid user, the method logs the user in.
- The logout method logs out a user. If you specified Browser Dialog Box as the login page type, it also calls the closeBrowser method to close the browser window. This behavior is necessary because the browser continues to send the old login credentials after the user logs out, and the cflogin tag will automatically use them and log the user in again.
- The closeBrowser method closes the browser window or tells the user to close the browser window to complete the logout , depending on the browser type.
- mm_wizard_login. cfm This file contains a ColdFusion login form. The wizard generates this file for all options, but does not use it if you specify Browser Dialog login.
- index.cfm or mm_wizard_index. cfm The wizard generates an index. cfm page if the directory does not have one; otherwise, creates an mm_wizard_index. cfm page. These pages let you test the generated login code before you implement your application, or without using any of your standard application pages. To test your login, open the index. cfm page in your browser.
Modifying the login code for your application
The Login Wizard creates a basic framework for authenticating a user. Customize this framework to meet the needs of your application. Typical security-related changes include the following:
- Providing user-specific role information in the cflogin tag
- Authenticating users against a database
Providing user-specific role information
The Login Wizard sets all users in a single role. In mm_wizard_authenticate.cfc, the performlogin method is hard-coded to set the role to "user." The authentication routines handle roles differently. (For the details, see the mm_wizard_authenticate.cfc code.) If your application uses roles for authorization, change the authentication method to get and return valid role information, and change the performlogin method to use the information in the roles attribute of its cfloginuser tag.
Authenticating users against a database
If you use a database to maintain user IDs and passwords, create your login framework by specifying simple authentication, and modify the code to use the database. The following instructions describe a simple way to change the code to use a database. They do not include all the cleanup work (particularly, removing the hard-coded user name and password) needed for a well-formatted application.
Replace the following code:
<cfif sUserName eq uUserName AND sPassword eq uPassword> <cfset retargs.authenticated="YES"> <cfelse> <cfset retargs.authenticated="NO"> </cfif> <cfreturn retargs>
With code like the following:
<cfquery name="loginQuery" dataSource="#Application.DB#" > SELECT * FROM Users WHERE UserName = <cfqueryparam value="#uUserName#" CFSEQLType= 'CF_SQL_VARCHAR'AND password = <cfqueryparam value="#uPassword#" CFSEQLType='CF_SQL_VARCHAR'> </cfquery> <cfif loginQuery.recordcount gt 0> <cfset retargs.authenticated="YES"> <cfset retargs.roles=loginQuery.roles> <cfelse> <cfset retargs.authenticated="NO"> </cfif> <cfreturn retargs>
For greater security, consider using a hashed password. Do not store the password directly in the database; instead, use the hash function to create a secure password fingerprint, and store it in the database. When the user provides a password, use the Hash function on the submitted string and compare it with the value in the database.
Web server based authentication user security example
The following example shows how to implement user security using web-server-based basic authentication and two roles, user and administrator.
This example has two ColdFusion pages:
- The Application.cfc page logs the user into the ColdFusion security system and assigns the user to specific roles based on the user's ID.This page also includes the one-button form and logic for logging out a user, which appears at the top of each page.
- The securitytest.cfm page is a sample application page. It displays the logged-in user's roles.
This simple example does not provide a user log-out interface. Test the security behavior by adding your own pages to the same directory as the Application.cfc page.
Example: Application.cfc
The Application.cfc page consists of the following:
<cfcomponent> <cfset This.name = "Orders"> <cffunction name="OnRequestStart"> <cfargument name = "request" required="true"/> <cflogin> <cfif IsDefined("cflogin")> <cfif cflogin.name eq "admin"> <cfset roles = "user,admin"> <cfelse> <cfset roles = "user"> </cfif> <cfloginuser name = "#cflogin.name#" password = "#cflogin.password#" roles = "#roles#" /> <cfelse> <!--- This should never happen. ---> <h4>Authentication data is missing.</h4> Try to reload the page or contact the site administrator. <cfabort> </cfif> </cflogin> </cffunction> </cfcomponent>
Reviewing the code
The Application.cfc onRequestStart method executes before the code in each ColdFusion page in an application. For more information on the Application.cfc page and when it is executed, see Designing and Optimizing a ColdFusion Application.The following table describes the CFML code in Application.cfc and its function:
|
Description |
---|---|
<cfcomponent> |
Identifies the application and starts the onRequestStart method that runs at the starts of each request. The login information on this page only applies to this application. |
<cflogin> |
Executes if there is no logged-in user.Makes sure that the user is correctly logged in by the web server. (Otherwise, there would be no cflogin variable.)Sets a roles variable based on the user's ID. Assigns users named "admin" to the admin role. Assigns all other users to the users role. |
<cfloginuser name = "#cflogin.name#" password = "#cflogin.password#" roles = "#roles#" /> |
Logs the user into the ColdFusion security system and specifies the user's password, name, and roles. Gets the password and name directly from the cflogin structure. |
<cfelse> |
This code should never run, but if the user somehow got to this page without logging in to the web server, this message would display and ColdFusion would stop processing the request. |
</cfif> |
Ends the if/else block.Ends the cflogin tag body.Ends the onRequestStart method.Ends the Application component. |
Example: securitytest.cfm
The securitytest.cfm page shows how any application page uses ColdFusion user authorization features. The web server ensures the existence of an authenticated user, and the Application.cfc page ensures that the user is assigned to roles the page content appears. The securitytest.cfm page uses the IsUserInAnyRole and GetAuthUser functions to control the information that is displayed.
The securitytest.cfm page consists of the following:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Basic authentication security test page</title> </head> <body> <cfoutput> <h2>Welcome #GetAuthUser()#!</h2> </cfoutput> ALL Logged-in Users see this message.<br> <br> <cfscript> if (IsUserInRole("admin")) WriteOutput("Users in the admin role see this message.<br><br>"); if (IsUserInRole("user")) WriteOutput("Everyone in the user role sees this message.<br><br>"); </cfscript> </body> </html>
Reviewing the code
The following table describes the securitytest.cfm page CFML code and its function:
Code |
Description |
---|---|
<cfoutput> |
User is already logged in by Application.cfc. Displays a welcome message that includes the user's login ID. |
ALL Logged-in Users see this message.<br> |
Displays this message in all cases. The page does not display until a user is logged in. |
<cfscript> |
Tests whether the user belongs to each of the valid roles. If the user is in a role, displays a message with the role name.The user sees one message per role to which the user belongs. |
Application-based user security example
The following example shows how to implement user security by authenticating users and then allowing users to see or use only the resources that they are authorized to access.
This example has three ColdFusion pages:
- The Application.cfc page contains the authentication logic that checks whether a user is logged in, requests the login page if the user is not logged in, and authenticates the data from the login page. If the user is authenticated, it logs the user in.This page also includes the one-button form and logic for logging out a user, which appears at the top of each page.
- The loginform.cfm page displays the login form. The code on this page could also be included in Application.cfc.
- The securitytest.cfm page is a sample application page. It displays the logged-in user's roles.
Test the security behavior by adding your own pages to the same directory as the Application.cfc page.
The example gets user information from the LoginInfo table of the cfdocexamples database that is installed with ColdFusion. You can replace this database with any database containing UserID, Password, and Roles fields. The sample database contains the following data:
UserID |
Password |
Roles |
---|---|---|
BobZ |
Ads10 |
Employee,Sales |
JaniceF |
Qwer12 |
Contractor,Documentation |
RandalQ |
ImMe |
Employee,Human Resources,Manager |
Because spaces are meaningful in roles strings, do not follow the comma separators in the Roles fields with spaces.
Example: Application.cfc
The Application.cfc page consists of the following:
<cfcomponent> <cfset This.name = "Orders"> <cfset This.Sessionmanagement="True"> <cfset This.loginstorage="session"> <cffunction name="OnRequestStart"> <cfargument name = "request" required="true"/> <cfif IsDefined("Form.logout")> <cflogout> </cfif> <cflogin> <cfif NOT IsDefined("cflogin")> <cfinclude template="loginform.cfm"> <cfabort> <cfelse> <cfif cflogin.name IS "" OR cflogin.password IS ""> <cfoutput> <h2>You must enter text in both the User Name and Password fields. </h2> </cfoutput> <cfinclude template="loginform.cfm"> <cfabort> <cfelse> <cfquery name="loginQuery" dataSource="cfdocexamples"> SELECT UserID, Roles FROM LoginInfo WHERE UserID = '#cflogin.name#' AND Password = '#cflogin.password#' </cfquery> <cfif loginQuery.Roles NEQ ""> <cfloginuser name="#cflogin.name#" Password = "#cflogin.password#" roles="#loginQuery.Roles#"> <cfelse> <cfoutput> <H2>Your login information is not valid.<br> Please Try again</H2> </cfoutput> <cfinclude template="loginform.cfm"> <cfabort> </cfif> </cfif> </cfif> </cflogin> <cfif GetAuthUser() NEQ ""> <cfoutput> <form action="securitytest.cfm" method="Post"> <input type="submit" Name="Logout" value="Logout"> </form> </cfoutput> </cfif> </cffunction> </cfcomponent>
Reviewing the code
Code | Description |
---|---|
<cfcomponent> <cfset This.name = "Orders"> <cfset This.Sessionmanagement="True"> <cfset This.loginstorage="session"> <cffunction name="OnRequestStart"> <cfargument name = "request" required="true"/> |
Identifies the application, enables session management, and enables storing login information in the Session scope.Begins the definition of the onRequestStart method that runs at the starts of each request. |
<cfif IsDefined("Form.logout")> <cflogout> </cfif> |
If the user just submitted the logout form, logs out the user. The following cflogin tag runs as a result. |
<cflogin> <cfif NOT IsDefined("cflogin")> <cfinclude template="loginform.cfm"> <cfabort> |
Runs if there is no logged-in user.Tests to see if the user has submitted a login form. If not, uses cfinclude to display the form. The built-in cflogin variable exists and contains the user name and password only if the login form used j_username and j_password for the input fields.The cfabort tag prevents processing of any code that follows on this page. |
<cfelse> <cfif cflogin.name IS "" OR cflogin.password IS ""> <cfoutput> <h2>You must enter text in both the User Name and Password fields. </h2> </cfoutput> <cfinclude template="loginform.cfm"> <cfabort> |
Runs if the user submitted a login form. Tests to make sure that both name and password have data. If either variable is empty, displays a message, followed by the login form. The cfabort tag prevents processing of any code that follows on this page. |
<cfelse> <cfquery name="loginQuery" dataSource="cfdocexamples"> SELECT UserID, Roles FROM LoginInfo WHERE UserID = '#cflogin.name#' AND Password = '#cflogin.password#' </cfquery> |
Runs if the user submitted a login form and both fields contain data. Uses the cflogin structure's name and password entries to find the user record in the database and get the user's roles. |
<cfif loginQuery.Roles NEQ ""> <cfloginuser name="#cflogin.name#" Password = "#cflogin.password#" roles="#loginQuery.Roles#"> |
If the query returns data in the Roles field, logs in the user using the user's name and password and the Roles field from the database. In this application, every user must be in some role. |
<cfelse> <cfoutput> <H2>Your login information is not valid.<br> Please Try again</H2> </cfoutput> <cfinclude template="loginform.cfm"> <cfabort> |
Runs if the query did not return a role. If the database is valid, this means there was no entry matching the user ID and password. Displays a message, followed by the login form. The cfabort tag prevents processing of any code that follows on this page. |
</cfif> </cfif> </cfif> </cflogin> |
Ends the loginquery.Roles test code.Ends the form entry empty value test.Ends the form entry existence test.Ends the cflogin tag body. |
<cfif GetAuthUser() NEQ ""> <cfoutput> <form action="securitytest.cfm" method="Post"> <input type="submit" Name="Logout" value="Logout"> </form> </cfoutput> </cfif> |
If a user is logged-in, displays the Logout button.If the user clicks the button, posts the form to the application's (theoretical) entry page, index.cfm. Application.cfc then logs out the user and displays the login form. If the user logs in again, ColdFusion displays index.cfm. |
</cffunction> </cfcomponent> |
Ends the onRequestStart methodEnds the Application component. |
Example: loginform.cfm
The loginform.cfm page consists of the following:
<H2>Please Log In</H2> <cfoutput> <form action="#CGI.script_name#?#CGI.query_string#" method="Post"> <table> <tr> <td>user name:</td> <td><input type="text" name="j_username"></td> </tr> <tr> <td>password:</td> <td><input type="password" name="j_password"></td> </tr> </table> <br> <input type="submit" value="Log In"> </form> </cfoutput>
Reviewing the code
The following table describes the loginform.cfm page CFML code and its function:
Code |
Description |
---|---|
<H2>Please Log In</H2> |
Displays the login form. Constructs the form action attribute from CGI variables, with a ? character preceding the query string variable. This technique works because loginform.cfm is accessed by a cfinclude tag on Application.cfc, so the CGI variables are those for the originally requested page.'The form requests a user ID and password and posts the user's input to the page specified by the newurl variable.Uses the field names j_username and j_password. ColdFusion automatically puts form fields with these values in the cflogin.name and cflogin.password variables inside the cflogin tag. |
Example: securitytest.cfm
The securitytest.cfm page shows how any application page can use ColdFusion user authorization features. Application.cfc ensures the existence of an authenticated user before the page content appears. The securitytest.cfm page uses the IsUserInAnyRole and GetAuthUser functions to control the information that is displayed.
The securitytest.cfm page consists of the following:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Security test page</title> </head> <body> <cfoutput> <h2>Welcome #GetAuthUser()#!</h2> </cfoutput> ALL Logged-in Users see this message.<br> <br> <cfscript> if (IsUserInRole("Human Resources")) WriteOutput("Human Resources members see this message.<br><br>"); if (IsUserInRole("Documentation")) WriteOutput("Documentation members see this message.<br><br>"); if (IsUserInRole("Sales")) WriteOutput("Sales members see this message.<br><br>"); if (IsUserInRole("Manager")) WriteOutput("Managers see this message.<br><br>"); if (IsUserInRole("Employee")) WriteOutput("Employees see this message.<br><br>"); if (IsUserInRole("Contractor")) WriteOutput("Contractors see this message.<br><br>"); </cfscript> </body> </html>
Reviewing the code
The following table describes the securitytest.cfm page CFML code and its function:
Code |
Description |
---|---|
<cfoutput> |
Displays a welcome message that includes the user's login ID. |
ALL Logged-in Users see this message.<br> |
Displays this message in all cases. The page does not display until a user is logged in. |
<cfscript> |
Tests whether the user belongs to each of the valid roles. If the user is in a role, displays a message with the role name.Users see one message per role that they belong. |
Using an LDAP directory for security information
LDAP directories are often used to store security information. The following example of a cflogin tag checks an LDAP directory to authenticate the user and retrieve the user's roles.
For more information on using LDAP directories with ColdFusion, see Managing LDAP Directories.
<cfapplication name="Orders" sessionmanagement="Yes" loginstorage="Session"> <cflogin> <cfif isDefined("cflogin")> <!--- setting basic attributes ---> <cfset LDAP_root = "o=mycompany.com"> <cfset LDAP_server = "ldap.mycompany.com"> <cfset LDAP_port = "389"> <!--- Create the prefix and suffix parts of the user's DN. ---> <cfset userPrefix = "cn="> <cfset userSuffix = ",ou=Users,o=mycompany.com"> <!--- Concatenate the user's DN and use it to authenticate. ---> <cfset LDAP_username = userPrefix&cflogin.name&userSuffix> <!--- This filter will look for groups for containing the user's ID. ---> <cfset userfilter = "(&(objectClass=groupOfUniqueNames) (uniqueMember=#LDAP_username#))"> <!--- Search for groups containing the user's dn. The groups represent the user's roles. NOTE: Your LDAP permissions must allow authenticated users to search. groups. ---> <cftry> <cfldap action="QUERY" name="auth" attributes="cn" referral="yes" start="#LDAP_root#" scope="SUBTREE" server="#LDAP_server#" port="#LDAP_port#" filter="#userfilter#" username="#LDAP_username#" password="#cflogin.password#" > <cfcatch type="any"> <cfif FindNoCase("Invalid credentials", cfcatch.detail)> <cfoutput> <script>alert("User ID or Password invalid for user: #cflogin.name#")</script> </cfoutput> <cfabort> <cfelse> <cfoutput> <script>alert("Unknown error for user: #cflogin.name# #cfcatch.detail#")</script> </cfoutput> <cfabort> </cfif> </cfcatch> </cftry> <!--- If the LDAP query returned a record, the user is valid. ---> <cfif auth.recordcount> <cfloginuser name="#cflogin.name#" password="#cflogin.password#" roles="#valueList(auth.cn)#"> </cfif> </cfif> </cflogin>
Reviewing the code
The following table describes the code and its function. Comments and some tab characters have been removed for brevity.
Code |
Description |
---|---|
<cflogin> |
Starts the cflogin tag body. Sets several variables to the values used as attributes in the cfldap tag. Sets prefix and suffix values used to create a distinquished name (dn) for binding to the LDAP server. Creates the user's bind dn by concatenating the prefix and suffix with cflogin .name. This variable is used for authenticating the user to the LDAP server.Sets the filter used to search the directory and retrieve the user's group memberships. The group membership represents the user's roles within the organization. |
<cftry> |
In a cftry block, uses the user's concatenated dn to authenticate to the LDAP server and retrieve the common name (cn) attribute for groups to which the user is a member. If the authentication fails the LDAP server returns an error. Note: The LDAP permissions must allow an authenticated user to read and search groups in order for the query to return results. |
<cfcatch type="any"> |
Catches any exceptions. Tests to see if the error information includes the string "invalid credentials", which indicates that either the dn or password is invalid. If so, displays a dialog box with an error message indicating the problem. Otherwise, displays a general error message.If an error is caught, the cfabort tag ends processing of the request after displaying the error description.End of cfcatch and cftry blocks. |
<cfif auth.recordcount> |
If the authorization query returns a valid record, logs in the user. Uses the valueList function to create a comma-separated list of the users retrieved group memberships, and passes them in the cfloginuser roles attribute.Ends the initial isDefined("cflogin") cfif block .Ends the cflogin tag body
|