ReCaptcha for Rails - With AJAX Validation
Purpose
This plugin implements view helpers to generate ReCaptcha code,
to interface with the HTTP API for captcha verification, a DSL
to generate a before_filter
and the necessary hacky code to
implement AJAX captcha validation.
Implementation
For AJAX validation, a request to ReCaptcha HTTP server must be made in order to verify the user input, but captchas can be checked only once.
The plugin, thus, in case of a successful verification, saves
uses the Rails flash
to temporarily save this status in the
session and then the before filter skips the verification via
the ReCaptcha HTTP service.
Installation
Via RubyGems:
gem install panmind-recaptcha
Or via Rails Plugin:
rails plugin install git://github.com/Panmind/recaptcha.git
Usage
In your config/environment.rb:
Panmind::Recaptcha.set(
:private_key => 'your private key',
:public_key => 'your public key
)
In your controller, say, the UsersController
for a signup action:
require_valid_captcha :only => :create
private
def invalid_captcha
@user = User.new params[:user]
@user.errors.add_to_base('Captcha failed')
render :new, :layout => 'login'
end
The invalid_captcha
method is called by the plugin when captcha
verification fails, and must be overwritten or a NotImplementedError
exception will be thrown.
In your view:
<%= recaptcha :label => 'Are you human?', :theme => 'clean' %>
You can pass any RecaptchaOptions
valid option, as stated by the
service documentation. The only nonstandard option :label
is used
by the plugin to print a label before the captcha widget.
AJAX Validation
To cache the results of a successful captcha verification, you need
simply to pass the :ajax => true
option to the require_valid_captcha
controller method.
require_valid_captcha :only => :create, :ajax => true
When the form is validated via AJAX, the maybe successful result will
be saved in the flash
(thus set in the session store); when the form is
then submitted via a plain HTTP request, verification will be skipped.
On Panmind we use our jquery.ajax-validation
plugin, that you
can download from http://github.com/Panmind/jquery-ajax-nav and
the Javascript code located in the js/signup-sample.js
file.
On the backend, an example checker that returns different HTTP status code follows:
def signup_checker
# If no email was provided, return 400
if params[:email].blank?
render :nothing => true, :status => :bad_request and return
end
email = CGI.unescape(params[:email])
# more thorough checks on email should go here
# If an user with this email already exist, return 406
if User.exists?(['email = ?', email])
render :nothing => true, :status => :not_acceptable and return
end
unless valid_captcha?
invalid_captcha and return
end
save_solved_captcha # This method sets a flag in the flash
render :nothing => true, :status => :ok
end
Moreover, the client Javascript code should be informed via an
HTTP status when validation fails, thus the invalid_captcha
must contain a special render
when the request comes from XHR:
def invalid_captcha
# If the captcha is not valid, return a 412 (precondition failed)
render :nothing => true, :status => 412 and return true if request.xhr?
# Same invalid_captcha code as above
end
The latest XHR specification from the w3c states that cookies set by responses to requests sent via XHR are to be honored by the browser.
The code was tested with IE (6,7,8), Safari (4, 5), Firefox 3, Chrome 5 and Opera 10.
Security
As long as you use a session store backed on the server or cryptographically sign the cookies used by the session cookie store (as Rails does by default) there is no way to bypass the captcha when AJAX validation is enabled.
Compatibility
Tested with Rails 3.0.3 running under Ruby 1.9.2p0. running under Ruby 1.9.1-p378.