A few months ago I posted a quick guide to walk folks through adding CAPTCHA's to forms:
This guide made use of the excellent Lyla CAPTCHA component. One of the new features of ColdFusion 8 is a built in CAPTCHA generator. So let's take a look at how we can do it the CF8 way...
First off, let's start with a simple contact us style form. I won't go into details about this form. It's a basic self-posting form with validation for a name and comment box.1 <cfset showForm = true>
2 <cfparam name="form.name" default="">
3 <cfparam name="form.comments" default="">
4
5 <cfif isDefined("form.send")>
6 <cfset errors = "">
7
8 <cfif not len(trim(form.name))>
9 <cfset errors = errors & "You must include your name.<br />">
10 </cfif>
11
12 <cfif not len(trim(form.comments))>
13 <cfset errors = errors & "You must include your comments.<br />">
14 </cfif>
15
16 <cfif errors is "">
17 <!--- do something here --->
18 <cfset showForm = false>
19 </cfif>
20
21 </cfif>
22
23 <cfif showForm>
24
25 <cfoutput>
26 <p>
27 Please fill the form below.
28 </p>
29
30 <cfif isDefined("errors")>
31 <p>
32 <b>Correct these errors:<br />#errors#</b>
33 </p>
34 </cfif>
35
36 <form action="#cgi.script_name#" method="post" >
37 <table>
38 <tr>
39 <td>Name:</td>
40 <td><input name="name" type="text" value="#form.name#"></td>
41 </tr>
42 <tr>
43 <td>Comments:</td>
44 <td><textarea name="comments">#form.comments#</textarea></td>
45 </tr>
46 <tr>
47 <td> </td>
48 <td><input type="submit" name="send" value="Send Comments"></td>
49 </tr>
50 </table>
51 </form>
52 </cfoutput>
53
54 <cfelse>
55
56 <cfoutput>
57 <p>
58 Thank you for submitting your information, #form.name#. We really do care
59 about your comments. Seriously. We care a lot.
60 </p>
61 </cfoutput>
62
63 </cfif>
Hopefully nothing above is new to you. So lets start updating this with some CAPTCHA love. First off, creating a CAPTCHA in ColdFusion 8 is incredibly easy. It takes all of one tag:
2 <cfparam name="form.name" default="">
3 <cfparam name="form.comments" default="">
4
5 <cfif isDefined("form.send")>
6 <cfset errors = "">
7
8 <cfif not len(trim(form.name))>
9 <cfset errors = errors & "You must include your name.<br />">
10 </cfif>
11
12 <cfif not len(trim(form.comments))>
13 <cfset errors = errors & "You must include your comments.<br />">
14 </cfif>
15
16 <cfif errors is "">
17 <!--- do something here --->
18 <cfset showForm = false>
19 </cfif>
20
21 </cfif>
22
23 <cfif showForm>
24
25 <cfoutput>
26 <p>
27 Please fill the form below.
28 </p>
29
30 <cfif isDefined("errors")>
31 <p>
32 <b>Correct these errors:<br />#errors#</b>
33 </p>
34 </cfif>
35
36 <form action="#cgi.script_name#" method="post" >
37 <table>
38 <tr>
39 <td>Name:</td>
40 <td><input name="name" type="text" value="#form.name#"></td>
41 </tr>
42 <tr>
43 <td>Comments:</td>
44 <td><textarea name="comments">#form.comments#</textarea></td>
45 </tr>
46 <tr>
47 <td> </td>
48 <td><input type="submit" name="send" value="Send Comments"></td>
49 </tr>
50 </table>
51 </form>
52 </cfoutput>
53
54 <cfelse>
55
56 <cfoutput>
57 <p>
58 Thank you for submitting your information, #form.name#. We really do care
59 about your comments. Seriously. We care a lot.
60 </p>
61 </cfoutput>
62
63 </cfif>
1 <cfimage action="captcha" width="300" height="75" text="paris">
The width and height determine the size of the image. The text determines what text will be displayed on the CAPTCHA. You can also determine what fonts to use - as well as the difficulty level.
So that part is easy. Everything after that takes a little bit of work. The first thing you need to figure out is what text to use. In the example above I used a hard coded value, paris, but in the real world you wouldn't do that. If you do, spammers would get past your CAPTCHA rather quickly.
You can create a list of random words - but unless your list is pretty big, you will again have the issue of spammers being able to guess the word. Instead, I recommend a random set of letters. I've built a UDF just for this purpose. Let's take a look:
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>
This UDF simply creates a random string from 4 to 7 characters long. You can tweak that size all you want, but any more than 7 will probably tick off your visitors. Also note the range of characters. I removed things like 1 (number one), l (lower case 'el'), and I (upper case "eye') since they can be confusing. Thanks to the NYCFUG members for feedback on this.
So once we have the UDF, we can now generate random text. But now we have another problem. When we submit the form, we are going to need to validate that the text you entered is the same as the text in the image. To do that, we need to store the text. Imagine if we did this:
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>
1 <cfset captcha = makeRandomString()>
2 <input type="hidden" name="captchatext" value="#captcha#">
As you can imagine, this is not very secure. A spammer would simply look for the hidden form field. So we need to encrypt the string somehow. ColdFusion offers multiple ways of doing this. For example though I'll just hash it:
2 <input type="hidden" name="captchatext" value="#captcha#">
1 <cfset captcha = makeRandomString()>
2 <cfset captchaHash = hash(captcha)>
Then I can add the CAPTCHA to my form like so:
2 <cfset captchaHash = hash(captcha)>
1 <tr>
2 <td>Enter Text Below:</td>
3 <td><input type="text" name="captcha"></td>
4 </tr>
5 <tr>
6 <td colspan="2">
7 <cfimage action="captcha" width="300" height="75" text="#captcha#">
8 <input type="hidden" name="captchaHash" value="#captchaHash#">
9 </td>
10 </tr>
Now the form has both the captcha and the text in hashed form. The last step is to just add the new validation. I do this by hashing the user's text against the hidden form field:
2 <td>Enter Text Below:</td>
3 <td><input type="text" name="captcha"></td>
4 </tr>
5 <tr>
6 <td colspan="2">
7 <cfimage action="captcha" width="300" height="75" text="#captcha#">
8 <input type="hidden" name="captchaHash" value="#captchaHash#">
9 </td>
10 </tr>
1 <cfif hash(ucase(form.captcha)) neq form.captchaHash>
2 <cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />">
3 </cfif>
And that's it. I'm done. The complete template is below. Enjoy.
2 <cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />">
3 </cfif>
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 <cfparam name="form.captchaHash" default="">
23
24 <cfif isDefined("form.send")>
25 <cfset errors = "">
26
27 <cfif not len(trim(form.name))>
28 <cfset errors = errors & "You must include your name.<br />">
29 </cfif>
30
31 <cfif not len(trim(form.comments))>
32 <cfset errors = errors & "You must include your comments.<br />">
33 </cfif>
34
35 <cfif hash(ucase(form.captcha)) neq form.captchaHash>
36 <cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />">
37 </cfif>
38
39 <cfif errors is "">
40 <!--- do something here --->
41 <cfset showForm = false>
42 </cfif>
43
44 </cfif>
45
46 <cfif showForm>
47
48 <cfset captcha = makeRandomString()>
49 <cfset captchaHash = hash(captcha)>
50
51 <cfoutput>
52 <p>
53 Please fill the form below.
54 </p>
55
56 <cfif isDefined("errors")>
57 <p>
58 <b>Correct these errors:<br />#errors#</b>
59 </p>
60 </cfif>
61
62 <form action="#cgi.script_name#" method="post" >
63 <table>
64 <tr>
65 <td>Name:</td>
66 <td><input name="name" type="text" value="#form.name#"></td>
67 </tr>
68 <tr>
69 <td>Comments:</td>
70 <td><textarea name="comments">#form.comments#</textarea></td>
71 </tr>
72 <tr>
73 <td>Enter Text Below:</td>
74 <td><input type="text" name="captcha"></td>
75 </tr>
76 <tr>
77 <td colspan="2">
78 <cfimage action="captcha" width="300" height="75" text="#captcha#">
79 <input type="hidden" name="captchaHash" value="#captchaHash#">
80 </td>
81 </tr>
82 <tr>
83 <td> </td>
84 <td><input type="submit" name="send" value="Send Comments"></td>
85 </tr>
86 </table>
87 </form>
88 </cfoutput>
89
90 <cfelse>
91
92 <cfoutput>
93 <p>
94 Thank you for submitting your information, #form.name#. We really do care
95 about your comments. Seriously. We care a lot.
96 </p>
97 </cfoutput>
98
99 </cfif>
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 <cfparam name="form.captchaHash" default="">
23
24 <cfif isDefined("form.send")>
25 <cfset errors = "">
26
27 <cfif not len(trim(form.name))>
28 <cfset errors = errors & "You must include your name.<br />">
29 </cfif>
30
31 <cfif not len(trim(form.comments))>
32 <cfset errors = errors & "You must include your comments.<br />">
33 </cfif>
34
35 <cfif hash(ucase(form.captcha)) neq form.captchaHash>
36 <cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />">
37 </cfif>
38
39 <cfif errors is "">
40 <!--- do something here --->
41 <cfset showForm = false>
42 </cfif>
43
44 </cfif>
45
46 <cfif showForm>
47
48 <cfset captcha = makeRandomString()>
49 <cfset captchaHash = hash(captcha)>
50
51 <cfoutput>
52 <p>
53 Please fill the form below.
54 </p>
55
56 <cfif isDefined("errors")>
57 <p>
58 <b>Correct these errors:<br />#errors#</b>
59 </p>
60 </cfif>
61
62 <form action="#cgi.script_name#" method="post" >
63 <table>
64 <tr>
65 <td>Name:</td>
66 <td><input name="name" type="text" value="#form.name#"></td>
67 </tr>
68 <tr>
69 <td>Comments:</td>
70 <td><textarea name="comments">#form.comments#</textarea></td>
71 </tr>
72 <tr>
73 <td>Enter Text Below:</td>
74 <td><input type="text" name="captcha"></td>
75 </tr>
76 <tr>
77 <td colspan="2">
78 <cfimage action="captcha" width="300" height="75" text="#captcha#">
79 <input type="hidden" name="captchaHash" value="#captchaHash#">
80 </td>
81 </tr>
82 <tr>
83 <td> </td>
84 <td><input type="submit" name="send" value="Send Comments"></td>
85 </tr>
86 </table>
87 </form>
88 </cfoutput>
89
90 <cfelse>
91
92 <cfoutput>
93 <p>
94 Thank you for submitting your information, #form.name#. We really do care
95 about your comments. Seriously. We care a lot.
96 </p>
97 </cfoutput>
98
99 </cfif>
Comment 1 written by Will on 29 February 2008, at 7:32 AM
Comment 2 written by Raymond Camden on 29 February 2008, at 8:18 AM
Comment 3 written by Will on 29 February 2008, at 8:34 AM
Comment 4 written by Bob on 6 May 2008, at 9:49 AM
May I ask why you use the ucase function when validating in this line:
<cfif hash(ucase(form.captcha)) neq form.captchaHash>
Thanks.
Comment 5 written by Raymond Camden on 6 May 2008, at 10:00 AM
Comment 6 written by Nate on 8 May 2008, at 5:34 PM
I put an example up at http://www.centralscene.com/captcha/captcha.png (keyboard icon).
Thanks for the great example, and the help!
Comment 7 written by Raymond Camden on 9 May 2008, at 8:36 AM
You can control the text since - well - you have to. But what you are seeing is the font being one of the symbol fonts. CF's captcha support lets you specify fonts. I didn't do that in my example as I wanted the code to work on both Macs and Windows machines. In a real production environment you would want to specify a few fonts that you know folks can read.
Comment 8 written by Eric on 9 August 2008, at 3:02 PM
<cfscript>
for(i=1; i <= length; i++) {
char = mid(chars, randRange(1, len(chars)),1);
result&=char;
}
</cfscript>
Comment 9 written by Raymond Camden on 9 August 2008, at 3:24 PM
Comment 10 written by Radek on 22 September 2008, at 5:53 PM
Comment 11 written by Raymond Camden on 22 September 2008, at 7:58 PM
Comment 12 written by farshid on 13 October 2008, at 2:14 AM
Your solution is very user friend and cool,
Good luck in your life,
Comment 13 written by Mike Pacella on 12 January 2009, at 1:12 PM
Excellent post. I used a lot of your code as the basis for my captcha functionality.
One comment, though. What about the problem of a user who submits 1 for your form.captcha field, and then the hash of 1, (which is c4ca4238a0b923820dcc509a6f75849b) for the form.captchaHash field? Way to easy to break that IMO.
In order to fix this, one thing we've done is concatenated a secret suffix to the captcha text before hashing it. For example:
Let the secretSuffix = "iHateRobots".
Let the randomly generated string = "ABCDE";
We would now hash ("ABCDEiHateRobots") and store that in the form.captchaHash field. When checking for validity simply:
Let the user input = "ABCDE";
Hash the userInput & secretSuffix, check against your form.captchaHash field, and this will protect you against that basic CAPTCHA bypass.
With all of that having been said, this post is excellent and superbly captures the basics of creating a captcha. Nice job, and thanks again for the post!
- Mike
Comment 14 written by Raymond Camden on 12 January 2009, at 1:21 PM
Comment 15 written by Mike Pacella on 12 January 2009, at 3:08 PM
Comment 16 written by Beaglis on 14 January 2009, at 1:39 PM
Comment 17 written by Beaglis on 14 January 2009, at 2:04 PM
Anyone know how to make CF generate a captcha png without layers? Or some workaround to this?
Thanks.
Comment 18 written by Beaglis on 14 January 2009, at 2:32 PM
I save the captcha to a destination (use cfsavecontent as mentioned above), then I convert the image format to jpg, then I display the image with IMG tag.
Converting to JPG removes the layers.
Comment 19 written by levensok on 21 January 2009, at 5:37 PM
http://v3.lightspeedvt.net/ebay_reg/register.cfm
If you refresh the page a few time, the captcha image will by displayed and then broken seemingly at random.
Comment 20 written by Raymond Camden on 21 January 2009, at 7:59 PM
Comment 21 written by Andrew on 6 February 2009, at 6:47 AM
Comment 22 written by Geoff on 18 February 2009, at 6:14 PM
This has the advantage of alerting the user before submission that they've got it wrong, which is less stressful than waiting for a response to find out and having the characters change all over again.
It's not going to do much in the way of preventing brute force attacks etc as they'll be posting direct, but it does mitigate some of the negative effects on the user.
Comment 23 written by Lucas Sá on 12 March 2009, at 4:11 PM
A good solution would be save the hash in Session scope, but it has its limitations. It impedes the user to access two pages that requires captcha simultaneously, once the first captcha would be "nullified" by the second.
In this case, I would suggest a mix between both solutions, creating an associative array (map, or a structure in the case of CF) of captchas generated for that user and store it in Session scope with the hash and the value of captchas. Also, put the hash in a hidden field as in this example and verify that hash in the "table" of captchas when user submits. If captcha matches the hash, delete its array register.
Comment 24 written by Sam on 27 May 2009, at 5:46 AM
Standard captcha will cause problems for the blind and partially sighted. There is some good information about this on the W3C website;
http://www.w3.org/TR/2005/NOTE-turingtest-20051123...
Although these users make up a very small percentage of our visits, some websites need to incorporate alternatives to meet guidelines and to ensure nobody is excluded.
I have not seen an example yet of anybody using text to speech with coldfusion.
Your thoughts and ideas?
Comment 25 written by Raymond Camden on 27 May 2009, at 7:19 AM
Comment 26 written by peters on 3 June 2009, at 8:45 AM
Comment 27 written by Ron on 12 June 2009, at 9:39 AM
"Cannot find the config file. configFile=..."
If you refresh the page, then it's fine, but its the initial load of the page.
Other than that, great script!
Comment 28 written by Raymond Camden on 12 June 2009, at 10:07 AM
Comment 29 written by Ron on 12 June 2009, at 10:26 AM
Thanks for straightening me out.
Comment 30 written by Sam on 15 June 2009, at 7:09 AM
Did you have any success with the FreeTTS?
Comment 31 written by Raymond Camden on 15 June 2009, at 8:30 AM
Comment 32 written by Jeff on 23 June 2009, at 9:49 PM
Thanks for the post. Works great!
I find the captcha to always be legible but, is there a simple solution to provide a refresh link that doesn't clear the filled form fields?
Comment 33 written by Sam on 24 June 2009, at 2:56 AM
I use a JavaScript method;
<a href="javascript:location.reload(false)">Refresh</a>
You can see this in action here;
http://www.stratford.gov.uk/labs/feedback/thickbox...
Hope this helps!
Sam.
Comment 34 written by Sam on 24 June 2009, at 3:04 AM
Thinking caps back on!
Comment 35 written by Jeff on 24 June 2009, at 2:41 PM
Comment 36 written by Ron on 24 June 2009, at 3:01 PM
<cfif not len(trim(form.email)) or not isValid("email", form.email)>
<cfset error = error & "Please include a valid email address!<br>">
</cfif>
Comment 37 written by rencontres on 24 June 2009, at 3:12 PM
Comment 38 written by Jeff on 24 June 2009, at 3:35 PM
Comment 39 written by Holly Jones on 7 July 2009, at 11:01 AM
Comment 40 written by Jeff on 7 July 2009, at 5:07 PM
http://www.overdriveevent.com/contact-us.cfm
Thanks!
Comment 41 written by Raymond Camden on 7 July 2009, at 5:09 PM
<cfif errors is "">
<!--- do something here --->
<cfset showForm = false>
</cfif>
or you are missing the <cfif showForm> aroudn the form.
Comment 42 written by Mike H on 9 July 2009, at 7:48 AM
Comment 43 written by Raymond Camden on 9 July 2009, at 7:58 AM
You can, of course, make your own image, but then you need to be responsible for adding the text yourself (which means picking the random font, position, etc).
Comment 44 written by Jackie on 14 September 2009, at 9:19 AM
Comment 45 written by jason on 27 October 2009, at 9:04 AM
mydomain.com/CFFileServlet/_cf_captcha/_captcha_img-1287068910168207643.png
Is this a server setting that is incorrect? While I'm pretty good at coding CF.. I'm a bit clueless when it comes to server settings like this. I am using CF8 as well.
Thanks and great tutorial... Nice an easy... I just need to get mine to work!
Comment 46 written by Raymond Camden on 27 October 2009, at 9:05 AM
Comment 47 written by jason on 27 October 2009, at 9:16 AM
My code is the EXACT code, with nothing else on it as your page above...
I just turned on robust reporting and see it's messing with one of my components it seems. I'm even more lost now that I see the error.. it's usually opposite!
The sample form is located at:
http://www.hermanscentral.com/rfq/index3.cfm
I GREATLY appreciate the help
Comment 48 written by Raymond Camden on 27 October 2009, at 9:24 AM
Comment 49 written by jason on 27 October 2009, at 9:46 AM
Turns out this was triggering the custom 404.cfm BUT on removing that 404.. (just removing all fancy code and putting 404 on that 404.cfm) the image still doesn't pop up.
Any other ideas?
Comment 50 written by jason on 27 October 2009, at 11:14 AM
so create a virtual directory called CFFileServlet
Map it to (your coldfusion install) / tmpCache/CFFileServlet and there you have it.
You rule an thanks for the tutorial!
[Add Comment] [Subscribe to Comments]