Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion

Posted April 28, 2009 at 9:53 AM

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

 Launch code in new window » Download code as text file »

  • <!--- 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

 Launch code in new window » Download code as text file »

  • <!--- 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

 Launch code in new window » Download code as text file »

  • <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.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Other Searches  |  Print Page



Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Apr 28, 2009 at 10:22 AM // reply »
25 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 »
44 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 »
7,572 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 »
29 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 »
29 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 »
7,572 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 »
29 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...


Post Comment  |  Ask Ben

Recent Blog Comments
Mar 22, 2010 at 7:43 AM
Terms Of Service / Privacy Policy Document Generator
Thankyou for this very helpful form. You've made my life much easier today. I'll have a look around your site... I'm sure there's some more good stuff here..Thanks Dave ... read »
Mar 22, 2010 at 7:21 AM
Encountered "(. Incorrect Select Statement, Expecting a 'FROM', But Encountered '(' Instead, A Select Statement Should Have a 'FROM' Construct.
I got this exception now. In case you're using var-es local struct, CF gives you couple of "new" exceptions: Encountered "local. and Encountered "id. Incorrect Select List, Incorrect select colum ... read »
Mar 22, 2010 at 3:08 AM
Ask Ben: Selecting XML Attributes Given Other XML Attributes
Thanks for the response. I finally discovered that I was getting this error because I had cfsetting enablecfoutputonly="yes" in Application.cfc, and was neither setting it to false elsewhere nor brac ... read »
Mar 21, 2010 at 8:57 PM
The Bourne Ultimatum Starring Matt Damon And Julia Stiles
late to the party, but my observation is this: rewatch carefully for the platonic nature of the relationship between nicki and jason. she never flirts with him. he never comes on to her. they alway ... read »
Mar 21, 2010 at 7:40 PM
Is Simulating User-Input Events With jQuery Ever A Good Idea?
A couple of things. One you embed the initial state of of more-info in the CSS. IMHO, that behavior should be in jQuery: moreInfo.hide(); It shows that the behavior your toggling and closing is mor ... read »
Mar 21, 2010 at 3:59 PM
Exploring ColdFusion Component Runtime Class Properties And Serialization
@Elliott, according to Ben's experiment, serializeJSON() doesn't access the private data by default - it doesn't even access the getHair() method - so trying to clone a Girl.cfc via serializeJSON/des ... read »
Mar 21, 2010 at 3:49 PM
Ask Ben: Javascript String Replace Method
I'm confused a bit by what you are asking, but if had this sentence: The color, red, is in the style statement; style: red;. and wanted to remove all or change all of the commas, colons, and semi-c ... read »
Mar 21, 2010 at 3:13 PM
Ask Ben: Javascript String Replace Method
I am trying to make a java program to count the number of times that these punctuation marks occur in a body of text: , : ; . ! - ' " ? / \ I am using this piece to ferret out the commas: numcommas ... read »