Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion

Posted April 28, 2009 at 9:53 AM by Ben Nadel

Tags: ColdFusion, Ask Ben

I'm working with a remote sms interface through a web service, and our endpoint is a cfc file that they hit. Our cfc returns wddx and is a remote cfc extended through ColdSpring. My question is: if we wanted to return an http statuscode of 401 Bad Credentials in the event Basic http authentication fails, will ColdFusion allow us to simply specify that in a cfheader tag w/in the component? I have tried this and it's simply returning 200 OK regardless. I don't want to have to go through the framework to have this process execute, which is the reason for the cfc endpoint instead.

Let me start this off by stating that I am not a security expert in any way and I happen to know very little about HTTP authorization. From what I have read (in preparation for this demo), there are several different forms of HTTP authorization that range in terms of complexity and security. The ColdFusion CFHTTP tag only supports BASIC authorization, not NTLM authorization. As such, that is the form that I will demonstrate.

I have set up a simple remote ColdFusion component with a single remote access method, Test(). Test() does nothing but return the string, "Method access successful!". While the Test() method is remote/public, the CFC itself, Remote.cfc, does check HTTP Authorization. I am now going to call this remote method using ColdFusion's CFHTTP and CFHTTPParam tags - once without login credentials and then once with credentials.

CFHTTP Without Credentials

  • <!--- Call CFC directly without authentication. --->
  • <cfhttp
  • method="get"
  • url="#strURL#"
  • result="objGet">
  •  
  • <cfhttpparam
  • type="url"
  • name="method"
  • value="Test"
  • />
  •  
  • </cfhttp>
  •  
  • <!--- Output the response. --->
  • <cfdump
  • var="#objGet#"
  • label="CFHttp Response"
  • />

When we try to access the remote Test() method without passing in any credentials, ColdFusion returns this response:

 
 
 
 
 
 
CFHTTP Request Without Proper Authorization Credentials. 
 
 
 

Notice that the ColdFusion application server return the status code 401 - Unauthorized. Our request failed authentication and the page request terminated.

Now, let's run the same demo, but this time, let's pass our Username and Password along with the CFHTTP tag attributes:

CFHTTP With Credentials

  • <!--- Call CFC directly WITH authentication. --->
  • <cfhttp
  • method="get"
  • url="#strURL#"
  • username="Molly"
  • password="hottie"
  • result="objGet">
  •  
  • <cfhttpparam
  • type="url"
  • name="method"
  • value="Test"
  • />
  •  
  • </cfhttp>
  •  
  • <!--- Output the response. --->
  • <cfdump
  • var="#objGet#"
  • label="CFHttp Response"
  • />

This time, when we pass in the HTTP authorization credentials with the ColdFusion CFHTTP tag, you can see that the returned status code is 200 and the FileContent of the response contains our remote Test() method return value, "Method access successful!". The request had proper authorization and the page was able to execute.

 
 
 
 
 
 
CFHTTP Request With Proper Authorization Credentials. 
 
 
 

OK, so how does my ColdFusion component enforce this Basic HTTP Authorization? It's doesn't do it implicitly - the authorization headers need to be checked manually. When a request comes into the ColdFusion server, we can check the request headers for authorization information using the GetHttpRequestData() method. One without any authorization information would look like this:

 
 
 
 
 
 
ColdFusion GetHTTPRequestData() Structure When No Authorization Credentials Are Passed. 
 
 
 

One with HTTP authorization credentials passes an Authorization key in the Headers struct:

 
 
 
 
 
 
ColdFusion GetHTTPRequestData() Structure When Authorization Credentials Are Passed. 
 
 
 

The Authorization key contains a two-item list. The first item, Basic, defines the type of authorization being used by the server. The second item (when using Basic authorization) is a Base64 encoded version of the given credentials in the following format:

username:password

Given this header, you can grab the Authorization key, decode the credentials, and then compare them against some internal login system. That is exactly what I'm doing in my Remote.cfc:

Remote.cfc

  • <cfcomponent
  • output="false"
  • hint="I am a remote-access testing component.">
  •  
  • <!---
  • Set up required credentials for API calls.
  •  
  • NOTE: This is not really the place to do this, but I am
  • doing this for the demo. Normally, you would want this
  • in some sort of application-centric area or management
  • system integration.
  • --->
  • <cfset THIS.Credentials = {
  • Username = "Molly",
  • Password = "hottie"
  • } />
  •  
  •  
  • <!--- ------------------------------------ --->
  •  
  •  
  • <!--- Check request authorization for every request. --->
  • <cfset THIS.CheckAuthentication() />
  •  
  •  
  • <!--- ------------------------------------ --->
  •  
  •  
  • <cffunction
  • name="Test"
  • access="remote"
  • returntype="string"
  • returnformat="json"
  • output="false"
  • hint="I am a remote-access test method.">
  •  
  • <cfreturn "Method access successful!" />
  • </cffunction>
  •  
  •  
  • <!--- ------------------------------------ --->
  •  
  •  
  • <cffunction
  • name="CheckAuthentication"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I check to see if the request is authenticated. If not, then I return a 401 Unauthorized header and abort the page request.">
  •  
  • <!---
  • Check to see if user is authorized. If NOT, then
  • return a 401 header and abort the page request.
  • --->
  • <cfif NOT THIS.CheckAuthorization()>
  •  
  • <!--- Set status code. --->
  • <cfheader
  • statuscode="401"
  • statustext="Unauthorized"
  • />
  •  
  • <!--- Set authorization header. --->
  • <cfheader
  • name="WWW-Authenticate"
  • value="basic realm=""API"""
  • />
  •  
  • <!--- Stop the page from loading. --->
  • <cfabort />
  •  
  • </cfif>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="CheckAuthorization"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I check to see if the given request credentials match the required credentials.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!---
  • Wrap this whole thing in a try/catch. If any of it
  • goes wrong, then the credentials were either non-
  • existent or were not in the proper format.
  • --->
  • <cftry>
  •  
  • <!---
  • Get the authorization key out of the header. It
  • will be in the form of:
  •  
  • Basic XXXXXXXX
  •  
  • ... where XXXX is a base64 encoded value of the
  • users credentials in the form of:
  •  
  • username:password
  • --->
  • <cfset LOCAL.EncodedCredentials = ListLast(
  • GetHTTPRequestData().Headers.Authorization,
  • " "
  • ) />
  •  
  • <!---
  • Convert the encoded credentials from base64 to
  • binary and back to string.
  • --->
  • <cfset LOCAL.Credentials = ToString(
  • ToBinary( LOCAL.EncodedCredentials )
  • ) />
  •  
  • <!--- Break up the credentials. --->
  • <cfset LOCAL.Username = ListFirst( LOCAL.Credentials, ":" ) />
  • <cfset LOCAL.Password = ListLast( LOCAL.Credentials, ":" ) />
  •  
  • <!---
  • Check the users request credentials against the
  • known ones on file.
  • --->
  • <cfif (
  • (LOCAL.Username EQ THIS.Credentials.Username) AND
  • (LOCAL.Password EQ THIS.Credentials.Password)
  • )>
  •  
  • <!--- The user credentials are correct. --->
  • <cfreturn true />
  •  
  • <cfelse>
  •  
  • <!--- The user credentials are not correct. --->
  • <cfreturn false />
  •  
  • </cfif>
  •  
  •  
  • <!--- Catch any errors. --->
  • <cfcatch>
  •  
  • <!---
  • Something went wrong somewhere with the
  • credentials, so we have to assume user is
  • not authorized.
  • --->
  • <cfreturn false />
  •  
  • </cfcatch>
  •  
  • </cftry>
  • </cffunction>
  •  
  • </cfcomponent>

For demonstration purposes, I am defining my possible credentials right in the CFC; you probably would be storing that information in a database, but for this demonstration, I'm keeping it simple. The Remote.cfc contains two utility methods: CheckAuthentication() and CheckAuthorization(). For every single page request, the Remote.cfc pseudo constructor calls the CheckAuthentication() method. This checks to see if the user is authorized (using CheckAuthorization()) and returns a 401 status code if they are not.

If the user is not trying to authorize (did not send credentials), there will be no Authorization header present (see above output). As such, I am wrapping my authorization scripts in a CFTry / CFCatch block. If any errors are raised, I know that the user either did not try to authorize or sent bad data; in either case, my CFCatch statement returns False to signify that the given user request is not authorized.

It is important to kill the current page request using CFAbort after the CFHeader tags otherwise the page would run normally, albeit with a 401 status code rather than a 200 status code. Now, this authorization check doesn't need to be in the CFC itself; it could easily be moved to a base CFC (from which all remote components inherit) or it could be put in the Application.cfc's OnRequestStart() event handler.

So again, I am no HTTP security expert, but from what I have found, this is how you can get ColdFusion to manually enforce Basic HTTP Authorization. I tried to use this same security when calling the CFC from the browser location, but this failed. FireFox seems to be trying to use the NTLM Authorization scheme with is more secure but significantly more complicated. Perhaps that's a topic for another post.




Reader Comments

Apr 28, 2009 at 10:22 AM // reply »
26 Comments

Excellent Write up as usual Ben -
I think my problem was (and I did try this but may have had a problem) that I didn't cfabort after the http status call, as I was returning 'xml' data to the browser through the response of the cfc (as wddx)- which would make it valid 'xml' regardless of the authentication passing etc - which is a bit of a catch 22.

I'll give it another go - rock on Ben! (Coming to cfObjective in Mpls?)


Apr 28, 2009 at 10:26 AM // reply »
66 Comments

Because I'm not one to let "it should go without saying" stop me from saying something, I'll say this:

Enforcing HTTP authentication with ColdFusion is almost always a terrible idea. If you're trying to do it, odds are that you're doing it wrong.

Think about it this way: HTTP authentication is a protocol-level construct. It's something that the web server needs to concern itself with, not your application. Let the web server do its job. There are ISAPI and Apache modules for doing HTTP authentication against databases, if that's what you need.

There's also the technical aspect: if you're going to code up HTTP authentication in CF, then you need to think not just about Basic, but also Digest and NTLM. Digest would be tricky to do in CF, and NTLM would be next to impossible. That, and there are browser-specific bugs you'd need to work around. Why sign up for that when there are security experts that have already figured that out for you?

Having said all of that, I do have a couple of pages where I check to make sure the user is logged in, and if not I throw a 401 to make them auth. But then I let the web server handle the actual authentication.

<cfif CGI.AUTH_USER EQ "">
<cfheader statuscode="401" statustext="Unauthorized">
<cfoutput><p>You need to login to access this area.</p></cfoutput>
<cfabort>
</cfif>

(You may need to configure your web server or use a different request header, but you get the point.)


Apr 28, 2009 at 10:35 AM // reply »
10,640 Comments

@Rick,

I have also rocked out the AUTH_USER aspect before and have enjoyed letting the server handle it. Not my problem.

To be fair, I did go back and forth with the askee a bit before writing this up. From what I gathered, they were doing CFHTTP requests for computer-to-computer communication and so I felt comfortable moving forward with the Basic-only authorization.

The NTLM and some others looked really complicated and far beyond my scope of understanding. The NTLM seemed to require 3 consecutive posts for full authorization. Crazy!


Apr 28, 2009 at 11:32 AM // reply »
30 Comments

Awesome writeup, and much more detailed than the one I put together awhile ago: http://www.mischefamily.com/nathan/index.cfm/2008/8/13/Basic-Authentication-With-ColdFusion

One thing to keep in mind, for Basic Authentication to be secure you need to be using SSL.


Apr 28, 2009 at 11:37 AM // reply »
30 Comments

One other thing I'll mention, if you try to use this method to secure a SOAP request you may run into issues with GetHTTPRequestData(). See my post above for more info.


Apr 28, 2009 at 11:47 AM // reply »
10,640 Comments

@Nathan,

That's too funny - I even commented on your post back then :) How quickly my mind forgets everything that I don't try out for myself.

I'll play around with the SOAP stuff - there's a whole host of functions in ColdFusion for SOAP... that I've never used.


Apr 28, 2009 at 12:10 PM // reply »
30 Comments

@Ben - Ha! I didn't notice your comments when I went back to read that post :) I tend to forget the stuff I do try out, let alone the stuff I only read about...


Feb 1, 2012 at 10:05 AM // reply »
4 Comments

I'm using this, or some variant thereof, to force authentication of a REST API I've been working on using Taffy. It all works perfectly in development and is a very slick solution (thanks as always, Ben!), but when I run it live it seems that the authentication details aren't passed along in the headers. The difference is I'm forced to use IIS6 on the production server, but I'm rocking IIS7 in development. (Yes. 'rocking' is the correct term. IIS7 is to IIS6 what diamonds are to compost.)
Has anyone had similar problems with IIS6?


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »
Feb 9, 2012 at 10:29 PM
Learning ColdFusion 9: Application-Specific Data Sources
@Ben, No offence, but if people were really wanting advanced features they would be using a platform like ASP.NET MVC. CFML is so structurally compromised as a tag-based scripting language that ... read »
Feb 9, 2012 at 10:03 PM
Subversion - Cleanup Failed To Process The Following Paths
@Leviaguirre, do you still have problems with this? ... read »