I don’t like CAPTCHAs. I don’t know anyone who does. But most forms need some sort of protection against spam, especially where heavyweight spam detection services (e.g. Akismet) aren’t suitable. The downfalls of CAPTCHAs are many – hard to read, annoying, impossible for those with vision difficulties – and the benefits are slim. So, a few months ago I wrote a little function (and I do mean little – like 10 lines of code) to generate a random math question to ask the user in plain text instead of a CAPTCHA. The idea was that a bot wouldn’t be able to answer it, since it requires some human logic. And you know what? It worked – spam went way down on our websites.
So, fast forward a few months and now I’m writing a CakePHP CMS, and I’ve decided to turn my ‘math captcha’ function into a Component. So here it is: my Math Captcha Component. This component generates a random equation and registers the answer as a session variable. The programmer can then check the form submitter’s answer against that registered answer using the validation function provided in the component.
Updated – January 19/2010: Download the Component from Github
Usage is really simple – I’ll run through putting it into an equally simple contact form, which looks a lot like the one done by Jonathan Snook. The Contact model looks like this (app/models/contact.php):
<?php
class Contact extends AppModel {
var $name = 'Contact';
var $useTable = false;
var $_schema = array(
'name' =>array('type' => 'string', 'length' => 100),
'email' =>array('type' => 'string', 'length' => 255),
'comments' =>array('type' => 'text')
);
var $validate = array(
'name' => array(
'rule'=>array('minLength', 1),
'message'=>'Please enter a name so the Geek know what to call you!' ),
'email' => array(
'rule'=>'email',
'message'=>'Please enter an email address so the Geek knows how to reach you.' ),
'details' => array(
'rule'=>array('minLength', 1),
'message'=> 'Don\'t forget to enter some comments.' )
);
}
?>
No DB; manual schema; just a placeholder, really.
The Contact controller is set up like this (app/controllers/contact_controller.php):
<?php
class ContactController extends AppController {
var $name = 'Contact';
var $uses = 'Contact';
var $components = array('RequestHandler', 'Email', 'Session', 'MathCaptcha');
function index() {
if ($this->RequestHandler->isPost()) {
$this->Contact->set($this->data);
if ($this->MathCaptcha->validates($this->data['Contact']['security_code'])) {
if ($this->Contact->validates()) {
$this->Email->to = Configure::read('SiteSettings.email_form_address');
$this->Email->subject = 'Contact from message from ' . $this->data['Contact']['name'];
$this->Email->from = $this->data['Contact']['email'];
$this->Email->send($this->data['Contact']['comments']);
}
} else {
$this->Session->setFlash(__('Please enter the correct answer to the math question.', true));
}
}
$this->set('mathCaptcha', $this->MathCaptcha->generateEquation());
}
}
?>
So, we’ve added MathCaptcha to our list of components. There are various configuration options which you can set when adding MathCaptcha to the $components array – the config array (with defaults) looks like this:
private $__defaults = array( 'operand' =&amp;amp;gt; '+', 'minNumber' =&amp;amp;gt; 1, 'maxNumber' =&amp;amp;gt; 5, 'numberOfVariables' =&amp;amp;gt; 2 );
In the index() method, you can see the usage: if we’ve got a POST request, we call the component’s validates() method and pass to it the relevant data from the form – the user’s answer to the question. If it validates then we continue with the rest of the data validation, otherwise we give an error message. You’ll notice that the generateEquation() method is called regardless; we want a new question generated each time the page loads.
Finally, we just need one line in the view to grab the ‘security_code’. Here’s the entire contact form (app/views/contact/index.ctp):
<?php
echo $form->create('Contact', array('url' => $this->here));
echo $form->input('name');
echo $form->input('email');
echo $form->input('comments');
echo $form->input('security_code', array('label' => 'Please Enter the Sum of ' . $mathCaptcha));
echo $form->end(array('name' => 'Send', 'class' => 'input_btn'));
?>
I’ve called the form field ‘security_code’, but you can call it whatever you want.
And that’s it! A plain text math ‘captcha’ in almost no time.
9 Responses to Math Captcha Component
deizel.
July 1st, 2009 at 4:20 am
Hi Jamie, not sure if you accidentally published this too soon then hit unpublish, but just a quick heads-up to let you know that it has appeared in Google Search and Google Reader with a broken link. Also, you can find it with your site search although it isn’t published to the front page of your blog. Useful component though!
Jamie Nay » Simple Math Question Captcha Component for CakePHP
July 1st, 2009 at 6:08 am
[...] Math Captcha Component [...]
Jamie
July 1st, 2009 at 6:12 am
Apparently it’s thanks my complete inability to use the WordPress scheduling tool properly. And that broken link was fixed within minutes – I can’t believe Google still picked it up! Yeesh. Thanks for the heads up.
Daniel
January 12th, 2010 at 9:18 pm
I love this component. It’s easy to integrate and works like a charm. Good job!
Todd
January 19th, 2010 at 6:14 pm
I noticed you have the option to adjust the settings like the “operand” but if you change it to – then it returns negative numnbers. Any plans on fixing it to work correctly with subtraction?
Todd
February 7th, 2010 at 11:12 am
Well I guess not. This works good for just adding numbers but thats it. I had to redo the whole thing to use subtraction.
Jamie
February 7th, 2010 at 12:07 pm
Sorry Todd – I’ve just been really busy lately, I haven’t been able to pay much attention to my blog or any of my public code. You’re right, I mistakenly put the operand option there before finishing that feature. To be honest it’s not a priority for me at this time, so I’ll probably just take out the option before I add support for multiple operands.
Morgan Leininger
June 10th, 2010 at 12:48 pm
Hi Jamie,
Thanks so much for sharing your code; this component seems a much better alternative than the terrible image based CAPTCHAS so prevalent today.
I have encountered a very strange bug with this component and Chrome (the browser). I am fairly sure the bug is with Chrome (I know, I know, it’s a purely server side component except insofar as Sessions involve a cookie…but it’s true). I have reported it to them.
I am using the stable CakePHP 1.2 branch. This component works as expected with IE 7, Firefox 3, and Safari 4. However, Chrome only a minute fraction of the time displays a question which is the correct answer to the value of the mathCaptcha written to Session. And that’s obviously just random probability based on the 1-5 min/max and + operand constraints.
My guess is that maybe Chrome does something different with .htaccess redirects that CakePHP is using in the course of processing requests. And/or it’s related to the way Chrome will grab “bad” URLs before Comcast…
In case you aren’t aware, Comcast has taken to sort of hijacking bad DNS requests; mistype a domain and you get a Comcast search page. Chrome (alone of the major browsers AFAIK) “pre-hijacks” those requests…which is a bit scary being as how they must essentially use their own DNS servers in preference to what I have set in my OS…
But in any case…it appears they somehow load the page twice very quickly. One run of $this->MathCaptcha->generateEquation() is sent to the display and a second run of $this->MathCaptcha->generateEquation() gets written to Session (though I’m not currently sure of the order).
Sadly, now that my brain is completely obsessed with this, I’ve been asked to ignore the Chrome users for now (they will almost never be able to validate a Simple Math Captcha, except for the few random equation collisions), but with Chrome creeping towards 15% in some estimates…
If you have any potential insights on this issue I’d love to hear them. If I hear back from the Chrome crew or crack this in some other way I’ll post back.
Thanks again for sharing.
Jamie
June 11th, 2010 at 2:11 pm
Hi Morgan,
I haven’t experienced that problem on Chrome, which is my main development browser. But thanks for the heads up – hopefully it’ll be useful information for other visitors.