Secure your forms with a simple Math Captcha
As soon as your application has a form that's available to everyone, you WILL be spammed! So what can we do about it? Well, one option is to have all forms secured by authentication... OR we can use a captcha.
As always, there are a couple of plugins / gems out there that do the traditional image captcha (you know, the beautiful images with random text on it). The problem is though, some of them suck: Some look hideous (like recaptcha) and others require a lot of effort to set up (see simple captcha).
What are the options?
Basic Math! Asking a (really) simple mathematical question will prevent spam, because stupid bots are just to stupid to answer it :)
What do we need for a math captcha? Basically a generator to create a question and a two-way hashing algorithm to encode (and later decode) the question/answer. Here's the code (in a rails app, you want to place this class into the lib folder):
require 'ezcrypto'
class MathCaptcha
CIPHER_KEY = "secret key"
CIPHER_SALT = "secret salt"
attr_reader :a, :b, :operator
def initialize
@a = (1..10).to_a.rand
@b = (1..10).to_a.rand
@operator = [:+, :*].rand
end
def initialize_from(secret)
yml = YAML.load(key.decrypt64(secret))
@a, @b, @operator = yml[:a], yml[:b], yml[:operator]
end
def correct?(value)
result == value.to_i
end
def encrypt
key.encrypt64 to_yaml
end
def self.decrypt(secret)
result = new
result.initialize_from secret
result
end
def question
"#{@a} #{@operator.to_s} #{@b} = ?"
end
protected
def to_yaml
YAML::dump({
:a => @a,
:b => @b,
:operator => @operator
})
end
private
def key
EzCrypto::Key.with_password CIPHER_KEY, CIPHER_SALT
end
def result
@a.send @operator, @b
end
end
The only interesting thing here is the code in the encrypt
and decrypt methods. Basically, what we do is
dump the relevant variables into a string using YAML and then encrypt/decrypt it
using ezcrypto.
Integrating the captcha into the controller and view
Since our MathCaptcha already defines all the interface methods
we need, the integration is straightforward. First the view
(note how we use the encrypted question):
/ ... other form code
%p
= hidden_field_tag :captcha_secret, @captcha.encrypt
= label_tag :captcha, @captcha.question
= text_field_tag :captcha, ""
%p
= submit_tag "Send"
And the controller is easy as well. All we do is to intantiate the same captcha question with the use of the secret:
require 'math_captcha'
class ContactsController < ApplicationController
def new
@captcha = MathCaptcha.new
end
def create
@captcha = MathCaptcha.decrypt(params[:captcha_secret])
unless @captcha.correct?(params[:captcha])
flash.now[:alert] = "Please make sure you answered the math question correct"
render :new
else
ContactsMailer.question(contact).deliver
flash[:notice] = "Thanks for your request. We will be in contact with you shortly"
redirect_to root_path
end
end
end
