Recaptcha, Devise, and Rails 3

My friend and I are working on a custom system where it is very important there is a low barrier to registration. I’m not keen on OpenID as I’ve read some of the headaches people have with reliability and I think for this particular system our own registration would be fine. I’ve always been a fan of Devise and we figured that Devise plus a Captcha would do the trick. We don’t want our users to have to click on a verification link via email to activate their account, or at least not right away.

To that end we’ve settled on the following gems for our Rails 3 application:

Devise
Recaptcha

We went through some difficulty at first trying to monkey patch Devise’s controllers because we didn’t want to have copies of views floating around. That didn’t work. Then I tried forking Devise and I learned that adding `self` methods to controllers makes big problems (or at least it did with Devise’s controllers, I assume this is a Rails thing I’m just not familiar with). Then I came across this on the Devise github wiki which is essentially what I am regurgitating here, step by step, with my observations for Rails 3.

Without further ado:

#Gemfile
⋮
gem 'recaptcha', :require => 'recaptcha/rails'
gem 'devise'
⋮
#console
$ bundle install

You will need to sign up for an API key for Recaptcha – assuming you are using that service and the gem I am. (With a bit of effort other Captcha gems could be used instead.) You’ll also need to configure the Recaptcha gem:

#./config/initializes/recaptcha.rb
# from github.com/ambethia/recaptcha README.rdoc
Recaptcha.configure do |config|
    config.public_key  = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy'
    config.private_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx'
  end

Be aware that if you host your project on a public repos you will want to add “./config/initializers/recaptcha.rb” to your `.gitignore` file and share the file amongst team members manually. (Or you can set environment variables as per Recaptcha’s README.rdoc.)

The Recaptcha gem relies on `Net::HTTP` to perform its verification of the generated Captcha. I didn’t see a `require “net/http”` anywhere except the tests and `init.rb` so I assume its common that this is included by default in Rails – probably 2.x. In my particular Rails 3 project, however, I had to add it myself as I don’t think `init.rb` was included along any execution path:

#./config/application.rb
⋮
require 'net/http'
⋮

Now you’ll have to go about setting up Devise. In this case I’m using the vanilla install, so:

#console
$ rails g devise:install
$ rake db:migrate

Then let’s create our custom `RegistrationsController` which is necessary to integrate the Recaptcha gem’s verification: (I created a new empty file and did not use any Rails generators for this part.)

#./app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
  def create
      if verify_recaptcha
        super
      else
        build_resource
        clean_up_passwords(resource)
        flash[:alert] = "There was an error with the recaptcha code below. Please re-enter the code and click submit."
        render_with_scope :new
      end
    end
end

But that's not all! We have to instruct Devise to use our custom controller and fortunately Devise will still use all its default controllers for anything we do not explicitly specify:

#./config/routes.rb
⋮
devise_for :users, :controllers => { :registrations => "registrations" }
⋮

And as we discovered when you specify your own controller there is no easy way to do a view path redirection - Rails controllers appear to be very inflexible in this way. We're going to have to generate Devise's views anyway, but then we're also going to have to copy the generated `registrations` new and edit views to the path Rails expects them to exist at.

#console
$ rails g devise:views
$ mkdir ./app/views/registrations
$ cp -r ./app/views/devise/registrations/*.html.erb ./app/views/registrations/

Let's add the code necessary to integrate the Recaptcha UI elements with our new registration view:

#./app/views/registrations/new.html.erb
⋮
<%= recaptcha_tags %>
<p><%= f.submit "Sign up" %></p>
⋮

And that should be it! Fire up your Rails server and go to http://localhost:3000/users/sign_up and your form should contain the Recaptcha UI elements. Fill the form out, roll face on keyboard for the Captcha verification, click submit and you should get an error message. Try again by this time entering the actual value from the Captcha and your account should be created.

You may be wondering about automated testing, especially with a browser controlled by Selenium. Browsing through the Recaptcha source code for verify.rb I discovered that the validation of the Recaptcha code is ignored when certain environment variables are set. In fact an array is defined at `Recaptcha::SKIP_VERIFY_ENV` which contains "test" and "cucumber" by default. So as long as your rake tasks are setting one of those environment variables OR you launch:

#console
$ rails s RAILS_ENV=test

... then Captcha validation should not occur.