Raymond Camden's Blog Rss

Using CAPTCHA in ColdFusion with reload powered by jQuery

39

Posted in jQuery, JavaScript, ColdFusion | Posted on 03-14-2011 | 5,254 views

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.

view plain print about
1<cffunction name="makeRandomString" returnType="string" output="false">
2    <cfset var chars = "23456789ABCDEFGHJKMNPQRS">
3    <cfset var length = randRange(4,7)>
4    <cfset var result = "">
5    <cfset var i = "">
6    <cfset var char = "">
7    
8    <cfscript>
9    for(i=1; i
<= length; i++) {
10        char = mid(chars, randRange(1, len(chars)),1);
11        result&=char;
12    }
13    </cfscript>
14        
15    <cfreturn result>
16</cffunction>
17
18<cfset showForm = true>
19<cfparam name="form.name" default="">
20<cfparam name="form.comments" default="">
21<cfparam name="form.captcha" default="">
22
23<cfif isDefined("form.send")>
24    <cfset errors = "">
25    
26    <cfif not len(trim(form.name))>
27        <cfset errors = errors & "You must include your name.<br />">

28    </cfif>
29
30    <cfif not len(trim(form.comments))>
31        <cfset errors = errors & "You must include your comments.<br />">

32    </cfif>
33
34    <cfif form.captcha neq session.captcha>
35        <cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />">
36    </cfif>
37        
38    <cfif errors is "">
39        <!--- do something here --->
40        <cfset showForm = false>
41    </cfif>
42    
43</cfif>
44
45<cfif showForm>
46
47    <cfset session.captcha = makeRandomString()>
48
49    <cfoutput>
50    <p>
51    Please fill the form below.
52    </p>
53    
54    <cfif isDefined("errors")>
55    <p>
56    <b>Correct these errors:<br />#errors#</b>
57    </p>
58    </cfif>
59    
60    <form action="#cgi.script_name#" method="post" >
61    <table>
62        <tr>
63            <td>Name:</td>
64            <td><input name="name" type="text" value="#form.name#"></td>
65        </tr>
66        <tr>
67            <td>Comments:</td>
68            <td><textarea name="comments">#form.comments#</textarea></td>
69        </tr>
70        <tr>
71            <td>Enter Text Below:</td>
72            <td><input type="text" name="captcha"></td>
73        </tr>
74        <tr>
75            <td colspan="2">
76            <cfimage action="captcha" width="300" height="75" text="#session.captcha#">
77            </td>
78        </tr>        
79        <tr>
80            <td>&nbsp;</td>
81            <td><input type="submit" name="send" value="Send Comments"></td>
82        </tr>
83    </table>
84    </form>
85    </cfoutput>
86    
87<cfelse>
88
89    <cfoutput>
90    <p>
91    Thank you for submitting your information, #form.name#. We really do care
92    about your comments. Seriously. We care a lot.
93    </p>
94    </cfoutput>
95    
96</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:

view plain print about
1<div id="captchaDiv"></div>

I used some jQuery to load it:

view plain print about
1$("#captchaDiv").load("showcaptcha.cfm");

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

view plain print about
1<cffunction name="makeRandomString" returnType="string" output="false">
2    <cfset var chars = "23456789ABCDEFGHJKMNPQRS">
3    <cfset var length = randRange(4,7)>
4    <cfset var result = "">
5    <cfset var i = "">
6    <cfset var char = "">
7    
8    <cfscript>
9    for(i=1; i
<= length; i++) {
10        char = mid(chars, randRange(1, len(chars)),1);
11        result&=char;
12    }
13    </cfscript>
14        
15    <cfreturn result>
16</cffunction>
17
18<cfset session.captcha = makeRandomString()>
19<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:

view plain print about
1Can'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!)

view plain print about
1$("#reloadLink").click(function(e) {
2    $("#captchaDiv").load("showcaptcha.cfm");            
3    e.preventDefault();
4});

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:

view plain print about
1<cfset showForm = true>
2<cfparam name="form.name" default="">
3<cfparam name="form.comments" default="">
4<cfparam name="form.captcha" default="">
5
6<cfif isDefined("form.send")>
7    <cfset errors = "">
8    
9    <cfif not len(trim(form.name))>
10        <cfset errors = errors & "You must include your name.<br />">

11    </cfif>
12
13    <cfif not len(trim(form.comments))>
14        <cfset errors = errors & "You must include your comments.<br />">
15    </cfif>
16
17    <cfif form.captcha neq session.captcha>
18        <cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />">
19    </cfif>
20        
21    <cfif errors is "">
22        <!--- do something here --->
23        <cfset showForm = false>
24    </cfif>
25    
26</cfif>
27
28<cfif showForm>
29
30    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
31    <script>
32    $(document).ready(function() {
33        $("#captchaDiv").load("showcaptcha.cfm");    
34        
35        $("#reloadLink").click(function(e) {
36            $("#captchaDiv").load("showcaptcha.cfm");            
37            e.preventDefault();
38        });
39    })
40    </script>
41
42    <cfoutput>
43    <p>
44    Please fill the form below.
45    </p>
46    
47    <cfif isDefined("errors")>
48    <p>
49    <b>Correct these errors:<br />#errors#</b>
50    </p>
51    </cfif>
52    
53    <form action="#cgi.script_name#" method="post" >
54    <table>
55        <tr>
56            <td>Name:</td>
57            <td><input name="name" type="text" value="#form.name#"></td>
58        </tr>
59        <tr>
60            <td>Comments:</td>
61            <td><textarea name="comments">#form.comments#</textarea></td>
62        </tr>
63        <tr>
64            <td>Enter Text Below:</td>
65            <td><input type="text" name="captcha"></td>
66        </tr>
67        <tr>
68            <td colspan="2">
69            <div id="captchaDiv"></div>
70            Can't read? <a href="" id="reloadLink">Reload</a>
71            </td>
72        </tr>        
73        <tr>
74            <td>&nbsp;</td>
75            <td><input type="submit" name="send" value="Send Comments"></td>
76        </tr>
77    </table>
78    </form>
79    </cfoutput>
80    
81<cfelse>
82
83    <cfoutput>
84    <p>
85    Thank you for submitting your information, #form.name#. We really do care
86    about your comments. Seriously. We care a lot.
87    </p>
88    </cfoutput>
89    
90</cfif>

Comments

[Add Comment] [Subscribe to Comments]

Nice :-)

I'm just using --- onclick="window.location.reload();return false;" --- on an anchor next to the image ...
Hi ray, I had also created the one before long, You can check this tutorial out!

http://tutorial571.easycfm.com/
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?
@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.
Ray, Your demo link doesn't work (for me) using IE7. Safari for windows and Firefox was OK.
Works in IE8. Can't test in 7. If you figure out why, please let us know.
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...
Brian - mod my JS to add a ?x= random number to the end. I bet that fixes it.
An added piece to this puzzle... clearing the cookie, forced a refresh.
Works in IE7 if temporary internet files set to 'Every time I visit the webpage', but not if set to 'Automatically'
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.
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;
heh I keep saying "add a random # to the url" - is no one seeing that?? ;)
Good one!
@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.
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.
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.
Darn nice tip there Josh. Thanks!
Have you thought about accessibility?
Not yet.
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.
A Google turned up this:

http://www.bennadel.com/blog/1289-ColdFusion-8-0-1...

Are you up to date with your hot fixes?
Hi Ray -

Where does

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

go in the form so the captcha image loads?
I'm confused. Do you not see it in the code template above? The last one in the blog entry?
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.
When you first hit the form does it load ok? When you do the refresh - what do you see with Firebug/Chrome Dev Tools?
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 ?
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.
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!
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.
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?
@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?
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.
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.
"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?
Yes, IE users see the same captcha and are unable to reload the captcha. The page in question is http://academicaffairs.iupui.edu/faculty-advanceme...
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();
   });
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?
That got it. Thanks for the assistance, Ray.

[Add Comment] [Subscribe to Comments]