Using CAPTCHA in ColdFusion with reload powered by jQuery

This post is more than 2 years old.

A few years ago I wrote a quick blog entry on working with CAPTCHA and ColdFusion. (Quick and dirty CAPTCHA Guide - for ColdFusion 8) A reader on that entry asked if it was possible to add a "reload if you can't read feature" to the CAPTCHA. We've all seen CAPTCHAs before that are impossible to read. For many of these forms you have no choice but to submit the form and hope the next one is better. But more and more forms are allowing you to reload just the CAPTCHA. Here is a quick example of how I modified the earlier blog entry to support this.

To start off - I modified my code from the old entry to avoid using a hash of the CAPTCHA for validation. Instead I stored the value in the session scope and verified it there. Here is the modified template. I'm not going to explain it since it's 95% the same as the old blog entry, but as you can see I now don't bother with a hash.

<cffunction name="makeRandomString" returnType="string" output="false"> <cfset var chars = "23456789ABCDEFGHJKMNPQRS"> <cfset var length = randRange(4,7)> <cfset var result = ""> <cfset var i = ""> <cfset var char = "">

&lt;cfscript&gt;
for(i=1; i &lt;= length; i++) {
	char = mid(chars, randRange(1, len(chars)),1);
	result&=char;
}
&lt;/cfscript&gt;
	
&lt;cfreturn result&gt;

</cffunction>

<cfset showForm = true> <cfparam name="form.name" default=""> <cfparam name="form.comments" default=""> <cfparam name="form.captcha" default="">

<cfif isDefined("form.send")> <cfset errors = "">

&lt;cfif not len(trim(form.name))&gt;
	&lt;cfset errors = errors & "You must include your name.&lt;br /&gt;"&gt;
&lt;/cfif&gt;

&lt;cfif not len(trim(form.comments))&gt;
	&lt;cfset errors = errors & "You must include your comments.&lt;br /&gt;"&gt;
&lt;/cfif&gt;

&lt;cfif form.captcha neq session.captcha&gt;
	&lt;cfset errors = errors & "You did not enter the right text. Are you a spammer?&lt;br /&gt;"&gt;
&lt;/cfif&gt;
	
&lt;cfif errors is ""&gt;
	&lt;!--- do something here ---&gt;
	&lt;cfset showForm = false&gt;
&lt;/cfif&gt;

</cfif>

<cfif showForm>

&lt;cfset session.captcha = makeRandomString()&gt;

&lt;cfoutput&gt;
&lt;p&gt;
Please fill the form below.
&lt;/p&gt;

&lt;cfif isDefined("errors")&gt;
&lt;p&gt;
&lt;b&gt;Correct these errors:&lt;br /&gt;#errors#&lt;/b&gt;
&lt;/p&gt;
&lt;/cfif&gt;

&lt;form action="#cgi.script_name#" method="post" &gt;
&lt;table&gt;
	&lt;tr&gt;
		&lt;td&gt;Name:&lt;/td&gt;
		&lt;td&gt;&lt;input name="name" type="text" value="#form.name#"&gt;&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;Comments:&lt;/td&gt;
		&lt;td&gt;&lt;textarea name="comments"&gt;#form.comments#&lt;/textarea&gt;&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;Enter Text Below:&lt;/td&gt;
		&lt;td&gt;&lt;input type="text" name="captcha"&gt;&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td colspan="2"&gt;
		&lt;cfimage action="captcha" width="300" height="75" text="#session.captcha#"&gt;
		&lt;/td&gt;
	&lt;/tr&gt;		
	&lt;tr&gt;
		&lt;td&gt;&nbsp;&lt;/td&gt;
		&lt;td&gt;&lt;input type="submit" name="send" value="Send Comments"&gt;&lt;/td&gt;
	&lt;/tr&gt;
&lt;/table&gt;
&lt;/form&gt;
&lt;/cfoutput&gt;

<cfelse>

&lt;cfoutput&gt;
&lt;p&gt;
Thank you for submitting your information, #form.name#. We really do care
about your comments. Seriously. We care a lot.
&lt;/p&gt;
&lt;/cfoutput&gt;

</cfif>

If you want to run this, you can do so here. But that isn't the interesting one. Let's look at a nicer version.

In order to support reloading just the CAPTCHA, I moved the CAPTCHA display into a DIV I'll be loading via Ajax. So where before I had the cfimage tag, now I have just this:

<div id="captchaDiv"></div>

I used some jQuery to load it:

$("#captchaDiv").load("showcaptcha.cfm");

showcaptcha.cfm now includes the logic to generate a random CAPTCHA string and to store/render it.

<cffunction name="makeRandomString" returnType="string" output="false"> <cfset var chars = "23456789ABCDEFGHJKMNPQRS"> <cfset var length = randRange(4,7)> <cfset var result = ""> <cfset var i = ""> <cfset var char = "">

&lt;cfscript&gt;
for(i=1; i &lt;= length; i++) {
	char = mid(chars, randRange(1, len(chars)),1);
	result&=char;
}
&lt;/cfscript&gt;
	
&lt;cfreturn result&gt;

</cffunction>

<cfset session.captcha = makeRandomString()> <cfimage action="captcha" text="#session.captcha#" width="300" height="75">

Ok, so how do we handle reload? After my div, I added a quick link:

Can't read? <a href="" id="reloadLink">Reload</a>

And then wrote a lot of JavaScript to handle clicks there. (OK, maybe not a lot of code - but jQuery saves me such much time I've got to pretend I actually work!)

$("#reloadLink").click(function(e) { $("#captchaDiv").load("showcaptcha.cfm"); e.preventDefault(); });

And that's it. The form page has changed quite a bit so I'll display it completely below. Before that you can demo it here:

<cfset showForm = true> <cfparam name="form.name" default=""> <cfparam name="form.comments" default=""> <cfparam name="form.captcha" default="">

<cfif isDefined("form.send")> <cfset errors = "">

&lt;cfif not len(trim(form.name))&gt;
	&lt;cfset errors = errors & "You must include your name.&lt;br /&gt;"&gt;
&lt;/cfif&gt;

&lt;cfif not len(trim(form.comments))&gt;
	&lt;cfset errors = errors & "You must include your comments.&lt;br /&gt;"&gt;
&lt;/cfif&gt;

&lt;cfif form.captcha neq session.captcha&gt;
	&lt;cfset errors = errors & "You did not enter the right text. Are you a spammer?&lt;br /&gt;"&gt;
&lt;/cfif&gt;
	
&lt;cfif errors is ""&gt;
	&lt;!--- do something here ---&gt;
	&lt;cfset showForm = false&gt;
&lt;/cfif&gt;

</cfif>

<cfif showForm>

&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"&gt;&lt;/script&gt;
&lt;script&gt;
$(document).ready(function() {
	$("#captchaDiv").load("showcaptcha.cfm");	
	
	$("#reloadLink").click(function(e) {
		$("#captchaDiv").load("showcaptcha.cfm");			
		e.preventDefault();
	});
})
&lt;/script&gt;

&lt;cfoutput&gt;
&lt;p&gt;
Please fill the form below.
&lt;/p&gt;

&lt;cfif isDefined("errors")&gt;
&lt;p&gt;
&lt;b&gt;Correct these errors:&lt;br /&gt;#errors#&lt;/b&gt;
&lt;/p&gt;
&lt;/cfif&gt;

&lt;form action="#cgi.script_name#" method="post" &gt;
&lt;table&gt;
	&lt;tr&gt;
		&lt;td&gt;Name:&lt;/td&gt;
		&lt;td&gt;&lt;input name="name" type="text" value="#form.name#"&gt;&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;Comments:&lt;/td&gt;
		&lt;td&gt;&lt;textarea name="comments"&gt;#form.comments#&lt;/textarea&gt;&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td&gt;Enter Text Below:&lt;/td&gt;
		&lt;td&gt;&lt;input type="text" name="captcha"&gt;&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td colspan="2"&gt;
		&lt;div id="captchaDiv"&gt;&lt;/div&gt;
		Can't read? &lt;a href="" id="reloadLink"&gt;Reload&lt;/a&gt;
		&lt;/td&gt;
	&lt;/tr&gt;		
	&lt;tr&gt;
		&lt;td&gt;&nbsp;&lt;/td&gt;
		&lt;td&gt;&lt;input type="submit" name="send" value="Send Comments"&gt;&lt;/td&gt;
	&lt;/tr&gt;
&lt;/table&gt;
&lt;/form&gt;
&lt;/cfoutput&gt;

<cfelse>

&lt;cfoutput&gt;
&lt;p&gt;
Thank you for submitting your information, #form.name#. We really do care
about your comments. Seriously. We care a lot.
&lt;/p&gt;
&lt;/cfoutput&gt;

</cfif>

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Edward - Florida SEO posted on 3/15/2011 at 8:18 AM

Nice :-)

I'm just using --- onclick="window.location.reload();return false;" --- on an anchor next to the image ...

Comment 2 by Misty posted on 3/15/2011 at 8:20 AM

Hi ray, I had also created the one before long, You can check this tutorial out!

http://tutorial571.easycfm....

Comment 3 by Jonathan posted on 3/15/2011 at 12:04 PM

Hi Ray, you placed the captcha string in session, that means only one captcha at a time per session... so the user can't process a different form on a different tab in the browser. I was thinking you can return the captcha image in xml, and the xmlroot includes the captcha string. and when the xml is returned, the JS creates a hidden input with the value captcha string. what do you think?

Comment 4 by Raymond Camden posted on 3/15/2011 at 3:13 PM

@Jonathan: You could easily support N captchas and N forms by using different names. So if this form was a contact form, I could have used session.contactcaptcha.

Comment 5 by Rich Hefter posted on 3/15/2011 at 5:06 PM

Ray, Your demo link doesn't work (for me) using IE7. Safari for windows and Firefox was OK.

Comment 6 by Raymond Camden posted on 3/15/2011 at 5:13 PM

Works in IE8. Can't test in 7. If you figure out why, please let us know.

Comment 7 by Brian posted on 3/15/2011 at 6:55 PM

I have an instance of IE7 also -- it may be an image caching issue -- Even a forced refresh of the page doesn't change the captcha graphic <shrug>

Hard to tell without Firebug support...

Comment 8 by Raymond Camden posted on 3/15/2011 at 7:04 PM

Brian - mod my JS to add a ?x= random number to the end. I bet that fixes it.

Comment 9 by Brian posted on 3/15/2011 at 7:05 PM

An added piece to this puzzle... clearing the cookie, forced a refresh.

Comment 10 by Chris Bowyer posted on 3/15/2011 at 7:07 PM

Works in IE7 if temporary internet files set to 'Every time I visit the webpage', but not if set to 'Automatically'

Comment 11 by Raymond Camden posted on 3/15/2011 at 7:11 PM

Yep, adding a random # to the URL would fix that. I haven't done it in a while, but I used to add a "iesucks" to the URL for stuff like this where the value was random.

Comment 12 by JP posted on 3/15/2011 at 7:16 PM

It's most likely a caching issue... try adding a random URL variable to the showcaptcha.cfm request... like:

x = Math.floor(Math.random()*100000);
"showcaptcha.cfm?x=" + x;

Comment 13 by Raymond Camden posted on 3/15/2011 at 7:17 PM

heh I keep saying "add a random # to the url" - is no one seeing that?? ;)

Comment 14 by Chris Bowyer posted on 3/15/2011 at 7:18 PM

Good one!

Comment 15 by brian posted on 3/15/2011 at 8:23 PM

@Ray yup, adding a random # to the URL does make the diff. You rat. You forced me to go read some jQuery docs. I've never played with it before -- not a desired interface at client sites that are remote and bandwidth throttled. Still not something I can use here, but an interesting exercise, nonetheless.

Comment 16 by Raymond Camden posted on 3/15/2011 at 8:25 PM

Sorry - what can't you use? jQuery? I made use of the remote library but you don't have to. You can download the jQuery library to your local web server.

Comment 17 by Josh posted on 3/15/2011 at 8:30 PM

Since I've never found a time where I'd want a browser to cache my ajax requests, I add this line to the top of my main page.
$.ajaxSetup ({
// Disable caching of ANY AJAX responses.
cache: false
});

This tells jquery to add the current date string to the end of the url each time a request is made. Simple and you never have to think about request caching again.

Comment 18 by Raymond Camden posted on 3/15/2011 at 8:46 PM

Darn nice tip there Josh. Thanks!

Comment 19 by Drew posted on 4/15/2011 at 11:29 PM

Have you thought about accessibility?

Comment 20 by Raymond Camden posted on 4/16/2011 at 5:27 AM

Not yet.

Comment 21 by Nathanael Waite posted on 6/8/2011 at 8:28 PM

I started experiencing on one of my load balanced CF9 servers with captcha. and the cfimage tag.

One Server: coldfusion.image.ImageWriter$ImageWritingException: An exception occurred while trying to write the image.

The other one rendered it fine.

My simple test:
<cfimage action="captcha" width="300" height="75" text="abcd3">

Does anyone have suggestions on why this happens or have they seen it before.

Comment 22 by Raymond Camden posted on 6/9/2011 at 6:23 AM

A Google turned up this:

http://www.bennadel.com/blo...

Are you up to date with your hot fixes?

Comment 23 by Adam posted on 6/14/2011 at 10:34 PM

Hi Ray -

Where does

$("#captchaDiv").load("showcaptcha.cfm");

go in the form so the captcha image loads?

Comment 24 by Raymond Camden posted on 6/14/2011 at 10:38 PM

I'm confused. Do you not see it in the code template above? The last one in the blog entry?

Comment 25 by Adam posted on 6/14/2011 at 10:49 PM

I do, however, the captcha image is not loading. I was asking for verification. I have simply copied and pasted all the code, but the image isn't showing. I was previous using the quick and dirty version with no problem and only wanted to add in the element refresh.

Comment 26 by Raymond Camden posted on 6/14/2011 at 10:50 PM

When you first hit the form does it load ok? When you do the refresh - what do you see with Firebug/Chrome Dev Tools?

Comment 27 by Adam posted on 6/14/2011 at 11:02 PM

Looks like:

The requested scope session has not been enabled.

Before session variables can be used, the session state management system must be enabled using the cfapplication tag.

Should this be enabled in the CF Admin ?

Comment 28 by Raymond Camden posted on 6/14/2011 at 11:03 PM

Add an App.cfc/App.cfm to your folder and turn it on. I should have been more clear that it would be required for this demo.

Comment 29 by Adam posted on 6/15/2011 at 12:32 AM

I added an application.cfm file with:

<cfapplication name = "captcha"
sessiontimeout = #CreateTimeSpan(0, 0, 0, 60)#
sessionmanagement = "Yes">

and the captcha now shows. Thanks for the help!

Comment 30 by Micah posted on 6/28/2011 at 8:08 PM

Actually, Ray, on your old quick and dirty captcha, if the viewer simply submits the form with an incorrect or empty captcha text field, it refreshes the captcha without changing any text they have entered in the other forms. I still prefer the hash anyway.

Comment 31 by Raymond Camden posted on 6/28/2011 at 8:13 PM

Sorry - what? What I read you saying is "If I submit the form and do not put the right captcha in, a new one is made." That's desired is it not?

Comment 32 by Adam posted on 9/6/2011 at 6:44 PM

@JP, @Ray -

Regarding the random URL variable, how do I incorporate the code

x = Math.floor(Math.random()*100000);
"showcaptcha.cfm?x=" + x;

into the showCaptcha.cfm code?

Comment 33 by Raymond Camden posted on 9/6/2011 at 6:52 PM

I'm not sure what your question means. You don't add anything to showcaptcha.cfm. The random number is there to just trick IE into not using the cache.

Comment 34 by Adam posted on 9/6/2011 at 8:21 PM

Thanks, Ray. I read JP's response about adding the URL variable and thought the solution was to add a variable to the showCaptcha code in order to make the variable appear in the URL. How do I make the random URL variable appear? This method is working in every browser except IE. Manually adding the variable works, but users won't be able to do it, so I need to code it to plug in automatically.

Comment 35 by Raymond Camden posted on 9/6/2011 at 8:29 PM

"How do I make the random URL variable appear?"

What do you mean? It isn't "appearing" - it is simply being added to the end of the main url. It's a way to trick IE into thinking you are asking for something it hasnt asked for before. (Technically it isn't a trick - it IS a new url.)

"This method is working in every browser except IE."

How is it not working for you? Remember that this fixes an issue where IE users would see the same captcha. So are your IE users seeing the same captcha? If so - can you share the URL of where this is so I can test it myself?

Comment 36 by Adam posted on 9/6/2011 at 8:47 PM

Yes, IE users see the same captcha and are unable to reload the captcha. The page in question is http://academicaffairs.iupu...

Comment 37 by Raymond Camden posted on 9/6/2011 at 8:52 PM

Um... because you didn't add the code. The code that appends the random crap. This is your JS:

$("#reloadLink").click(function(e) {
$("#captchaDiv").load("showcaptcha.cfm");
e.preventDefault();
});

Comment 38 by Adam posted on 9/6/2011 at 9:08 PM

So in adjusting that part of the code, this allows for the captcha to be reloaded, but only once:

$("#reloadLink").click(function(e) {
x = Math.floor(Math.random()*100000);
$("#captchaDiv").load("showcaptcha.cfm?x="+x);
e.preventDefault();
});

Is this incorrect?

Comment 39 by Adam posted on 9/6/2011 at 9:16 PM

That got it. Thanks for the assistance, Ray.

Comment 40 by steve ryan posted on 5/24/2012 at 5:41 PM

<!---MAKE A CAPTCHA--->
<cffunction name="getCaptchaContact" access="remote" output="false" returnType="string" returnFormat="JSON">

<cfset local.fileName = CreateUUID() />
<cfset local.captchaString = makeRandomString() />
<cfset session.captchaContact = local.captchaString />
<cfimage action="captcha" difficulty="low" text="#local.captchaString#" destination = "#local.fileName#.png" />
<cffile action="readbinary" file="/_cfc/#local.fileName#.png" variable="local.binimage">
<cffile action="delete" file="/_cfc/#local.fileName#.png" />
<cfset local.encodedImg = BinaryEncode(binimage, "Base64") />

<cfreturn local.encodedImg />

</cffunction>

This is probably lame. But this is how I have been making captcha's. I just ajax the captcha were ever I need it on my site.

Comment 41 by Raymond Camden posted on 5/24/2012 at 5:57 PM

Not lame - kinda cool. :)

Comment 42 by Nan posted on 9/11/2013 at 6:05 AM

Ray,
Your demo does not display captcha image any more. Do you have any suggestions? Is it anything to do with CF 10 upgrade?
Thanks for your help.

Nan

Comment 43 by Raymond Camden posted on 9/11/2013 at 6:13 AM

Yeah - the code should work fine if run on your own server.

Comment 44 by Nan posted on 9/11/2013 at 7:59 PM

Ray,
I've a strange problem. Your code works as such on CF 8 server on all 3 major browsers (FF, Chrome and IE 7, 8, 9). But it doesn't work on IE (8 and 9 tested) after I upgrade our dev server to CF 10. (we jumped CF9). Both production and dev environments are under SSL behind F5. Unfortunately it is an internal site so the URL won't load from outside.
Have you seen it before?
thanks

Comment 45 by Nan posted on 9/11/2013 at 8:09 PM

One more thing,
If I put the complete path on IE it loads the image as such.
CFFileServlet/_cf_captcha/_captcha_img1637508603629842317.png

I tried both --cfimage giving a name and destination as well as cfimage action=captcha, the result is same. the image doesn't load on IE and loads fine on FF and Chrome on CF 10 server.
Thanks for your help.

Comment 46 by Raymond Camden posted on 9/11/2013 at 9:48 PM

Not sure what to suggest. Do you see anything in IE's dev tools?

Comment 47 by Nan posted on 9/11/2013 at 10:46 PM

Figured it is something to do with JQery AJAX call to showcaptcha.cfm.
The dev server had debug output to the screen FF and Chrome didn't mind them. IE couldn't handle that but no errors anywhere. Simply no image displayed.

When I added <cfsetting showdebugoutput="no"> on top of showcaptcha.cfm. IE now loads it fine.

Sorry for the false alarm.

Comment 48 by Raymond Camden posted on 9/11/2013 at 11:35 PM

Ahhh - duh! :)