Digest Authentication in ColdFusion

I’m working with the ConstantContact API webservice, and ran into a slight problem. The API uses Digest Authentication, and ColdFusion does not support Digest Authentication in cfhttp. I found this out by reading in the live docs that:

The cfhttp tag does not support NTLM or Digest Authentication.

The ConstantContact forums pointed me to a custom tag cfx_http5. But I hate CFX tags. I like to place as few dependencies on the ColdFusion Administrator as possible when I code. CFX tags also tend not to make the trip the first time in server migrations or emergency moves. It might be my own prejudice but, no, there will be no custom tags.

Which leaves me to implementing some Java client, as luck would have it there was ample documentation on doing this. I just needed to use the open source Apache HTTP client. To work with them, I had to add them to the class path, or add an entry in the ColdFusion Administrator class path list. That would have bought me nothing, as I didn’t want to go through the administrator.

This left me looking for a way to dynamically call jar files. If only there was some way to dynamically call jar files in ColdFusion… preferably written by someone with a marsupial pouch…. Ohh wait, Mark Mandel wrote the Open Source project JavaLoader, which allows just that. Hmm, and Mark is a mammal from Australia. Things are looking good.

So I was able to build my http client with Digest Authentication. I added some code that formatted the response like a cfhttp struct, so that if there were other advantages to this method I could just swap out this for cfhttp in a pinch. The code is below in the extended section of this blog post.

I was trying to do 3 things with this post:

  1. Explain how to use Digest Authentication with ColdFusion
  2. Show how with ColdFusion Open Source community and ColdFusion’s ability to call Java, there isn’t much you can’t do with it.
  3. Further the rumor that Mark Mandel is a marsupial.

I hope I’ve done these three things for you.

var paths = arrayNew(1);
var rootPath = GetDirectoryFromPath(GetCurrentTemplatePath());
paths[1] = rootPath & "/jars/commons-httpclient-3.1.jar";
paths[2] = rootPath & "/jars/commons-codec-1.3.jar";
paths[3] = rootPath & "/jars/commons-logging-1.1.1.jar";
//create the loader
variables.loader = createObject("component", "javaloader.JavaLoader").init(paths);

var result = "";
var credentials = loader.create("org.apache.commons.httpclient.UsernamePasswordCredentials");
var HttpClient = loader.create("org.apache.commons.httpclient.HttpClient").init();
var httpGet = loader.create("org.apache.commons.httpclient.methods.GetMethod");
var AuthScope = loader.create("org.apache.commons.httpclient.auth.AuthScope");
var jURL = createObject("java", "java.net.URL").init(arguments.url);

if (len(arguments.username) and len(arguments.password) gt 0){
AuthScope.init(jURL.getHost(), arguments.port, arguments.realm);
credentials.init(arguments.Username, arguments.password);
httpClient.getState().setCredentials(authScope, credentials);
}

httpGet.init(arguments.url);
httpClient.executeMethod(httpGet);

result = convertHttpClientResponseToCFHTTPFormat(httpGet);
httpGet.releaseConnection();

return result;

var result = structNew();
var responseheader = structNew();
responseheader['Status_Code'] = httpGet.getStatusCode();
responseheader['Explanation'] = httpGet.getStatusText();
responseheader['Http_Version'] = httpGet.getEffectiveVersion().toString();
header = httpGet.getStatusLine().toString();

headers = httpGet.getResponseHeaders();

for (i=1; i lte ArrayLen(headers); i=i+1){
responseheader[getToken(headers[i], 1, ":")] = getToken(headers[i], 2, ":");
header = listAppend(header, headers[i], " ");
}

result['Charset'] = httpGet.getResponseCharSet();
result['ErrorDetail'] = "";
result['Filecontent'] = httpGet.getResponseBodyAsString();
result['Header'] = header;

if (structKeyExists(responseheader, 'Content-Type')){
result['Mimetype'] = GetToken(responseheader['Content-Type'], 1, ";");
}

result['Responseheader'] = responseheader;
result['Statuscode'] = responseheader['Status_Code'] & " " & responseheader['Explanation'];

if( not structKeyExists(responseheader, 'Content-Type') OR
FindNoCase("text", responseheader['Content-Type']) OR
FindNoCase("message", responseheader['Content-Type']) OR
FindNoCase("application/octet-stream", responseheader['Content-Type'])
){
result['Text'] = "YES";
}
else{
result['Text'] = "NO";
}

return result;

14 thoughts on “Digest Authentication in ColdFusion

  1. @Sami, I kinda rushed it out as a response to CF is dead nonsense today. I might give that a try, but I’m looking at a swamped next few weeks.

    Anyone who wants to steal the code and release on Riaforge can be my guest.

    Like

  2. Hi Guys
    you can do the same thing on windows with the XMLHTTP COM object.
    you don’t need to install anything because its part of the OS.

    Like

  3. Talk about perfect timing! Decided I wanted to get my work calendar, which is hosted on an OS X Caldav server, into my Google calendar. Trouble was Google doesn’t support remote calendars with authentication so I needed a CF proxy in between. This worked a treat, although for some reason I needed to put the port number in the URL string as well as the port attribute.
    Many thanks!

    Like

  4. I have to say great timing as well. I’m just starting to do some API programming with Constant Contact in a ColdFusion page. Make sure you use the "realm" argument with "api.constantcontact.com" as the value. Thanks for the post.

    Like

  5. You can do a post also, just copy the get function, replacing "get" with "post" and then:


    <cfargument name="url" type="string" required="TRUE" hint="The url to call." />
    <cfargument name="body" type="string" required="TRUE" hint="What to send to the URL" />

    ….

    var AuthScope = loader.create("org.apache.commons.httpclient.auth.AuthScope");
    var myStringRequestEntity = loader.create("org.apache.commons.httpclient.methods.StringRequestEntity");

    ….

    httpPost.init(arguments.url);
    httpPost.setRequestEntity(myStringRequestEntity.init(arguments.body, "application/atom+xml", "UTF-8"));

    ….

    NOTE: This is for posting an ATOM XML document as a string.

    Like

  6. Excellent! I’m about to need a constant contact API wrapper myself. If it’s alright w/you, I’ll use your code as a starting point and release it and the CC api on riaforge once they’re done.

    Like

  7. Constant Contact’s API is switching from Digest Authentication to Basic Authentication over HTTPS. Since you don’t indicate the authentication type (digest versus basic), it appears there’s nothing that needs to change in the code. We just need to make sure the URLs have "https" instead of "http." I’ll update you if anything changes after I test this strategy out.

    Like

  8. This begs the question – WHY don’t Adobe support NTLM in CFHTTP when the functionality is obviously there by using your method?!?!?!?! Dumbfounded!

    Thank for the fix BTW

    Martin

    Like

  9. Terrence,

    Please forgive a rookie question. Can you show me how to implement the code written above? I am new to some of this and I can’t seem to get it going.

    Thank you.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s