Adding reCAPTCHA v3 to Rails for Bot Protection
Overview
A web service I built with Rails was being hit by bot attacks on a regular basis, so I added reCAPTCHA v3 to the contact form.
Why reCAPTCHA v3 and not v2?
With v2, after you tick the I'm not a robot checkbox, there is a step that makes you pick images.
For example, if you are asked “Which of these show a billboard?”, the psychological burden is high (“How much of it counts as the billboard?”), and there is a chance the user will abandon the form.
What’s nice about v3?
v3 *1 scores user behavior on the page where it is installed and decides whether the visitor is a bot.
The more traffic it gets, the more accurate it becomes.
It places no burden at all on legitimate (non-bot) users while still blocking bots — it really makes you feel how far the world has come.

Is there a gem?
I didn’t use a gem this time.
The reasons were as follows:
gem 'recaptcha'does not support v3.gem 'new_google_recaptcha'does support v3, but it doesn’t return the score, which makes it hard to test.
There may be others out there, but I couldn’t find one at the time of writing.
First, issue a reCAPTCHA v3 key
Go to the reCAPTCHA console below and issue a key.
Select v3 and register the domain you are introducing it to.*2
Save the issued site key and secret key.
- Site key
- The key needed to obtain a token when a user accesses the site. It is fine to expose this to the public.
- Secret key
- The key needed when querying Google based on the token. Treat this as confidential information.
Implementation on the Rails side
This assumes Rails >= 5.2.
config/credentials.yml.enc
recaptcha:
secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Store the secret in your confidential credentials.
app/controllers/application_controller.rb
require 'net/http' require 'uri' class ApplicationController < ActionController::Base ... RECAPTCHA_MINIMUM_SCORE = 0.5 RECAPTCHA_ACTION = 'homepage' ... def verify_recaptcha?(token) secret_key = Rails.application.credentials.recaptcha[:secret_key] uri = URI.parse("https://www.google.com/recaptcha/api/siteverify?secret=#{secret_key}&response=#{token}") r = Net::HTTP.get_response(uri) j = JSON.parse(r.body) j['success'] && j['score'] > RECAPTCHA_MINIMUM_SCORE && j['action'] == RECAPTCHA_ACTION end end
As a shared method, I define the recaptcha authentication method `verify_recaptcha?`.
Here, a score of 0.5 or below is treated as a bot.
If you operate the page normally, you'll easily exceed that value.
config/locales/en.yml
en:
recaptcha:
errors:
verification_failed: 'reCAPTCHA Authorization Failed. Please try again later.'
This is the locale `en` configuration.
config/locales/ja.yml
ja:
recaptcha:
errors:
verification_failed: 'reCAPTCHA 認証失敗しました。しばらくしてからもう一度お試しください。'
This is the locale `ja` configuration.
app/controllers/hoges_controller.rb
class HogesController < ApplicationController def new; end def create unless verify_recaptcha?(params[:recaptcha_token]) flash.now[:recaptcha_error] = I18n.t('recaptcha.errors.verification_failed') return render action: :new end # something to do redirect_to hoge_finish_path end def finish; end end
The flow posts from new to create, runs the bot check via reCAPTCHA, and then
- OK → proceed to finish
- NG → return to new
is the design.
app/views/hoges/new.html.erb
<% if flash[:recaptcha_error] %> <div class="text"> <p><spacn class="error"><%= flash[:recaptcha_error] %></span></p> </div> <% end %> <%= form_tag({action: :create}, {method: :post}) do %> ... <input id="recaptcha_token" name="recaptcha_token" type="hidden"/> <%= submit_tag "送信する", :class => "submit-recaptcha btn", :disabled => true %> <% end %> <script src="https://www.google.com/recaptcha/api.js?render=<%= Settings.recaptcha.site_key %>&ver=3.0"></script> <script> grecaptcha.ready(function() { grecaptcha.execute('<%= Settings.recaptcha.site_key %>', {action: 'homepage'}).then(function(token) { $('#recaptcha_token').val(token); $('.submit-recaptcha').prop('disabled', false); }); }); </script>
Displaying the error message
<% if flash[:recaptcha_error] %> <div class="text"> <p><spacn class="error"><%= flash[:recaptcha_error] %></span></p> </div> <% end %>
Add the following name=recaptcha_token input tag inside <form> ~ </form>.
<input id="recaptcha_token" name="recaptcha_token" type="hidden"/>
Embed a script to obtain the reCAPTCHA token on page access.
<script src="https://www.google.com/recaptcha/api.js?render=<%= Settings.recaptcha.site_key %>&ver=3.0"></script> <script> grecaptcha.ready(function() { grecaptcha.execute('<%= Settings.recaptcha.site_key %>', {action: 'homepage'}).then(function(token) { $('#recaptcha_token').val(token); $('.submit-recaptcha').prop('disabled', false); }); }); </script>
When the reCAPTCHA token is obtained successfully, the following are executed.
- Set the token as the value of the id="recaptcha_token" input tag
- Enable the submit button
Regarding `<%= Settings.recaptcha.site_key %>`
this is configured on the assumption that `gem 'settingslogic'` is installed.
If you haven't installed it, and you just want to try out the process quickly, replace `<%= Settings.recaptcha.site_key %>` with the site key you obtained.*3
That completes the setup.
Try accessing the page
The reCAPTCHA badge will now always be displayed in the bottom-right corner of the page.
Viewing the aggregated information
When you look at the reCAPTCHA console, you'll probably see a display like the one below, with the aggregated information not yet reflected.
After a while, a graph like the following will start to be displayed.
Caveat
For example, if you access the site frequently for testing from a fixed IP such as an internal corporate IP, you'll be treated as a bot.
There is no IP whitelist on the reCAPTCHA side, so in that case you'll need to build an allowed-IP list on the Rails side.
That's all.
I hope you find it helpful.
*1:The latest version as of February 2019
*2:You can register multiple domains. If you want to change the aggregation or bot-prevention behavior per domain, issue keys individually. Also, issuing separate keys for RAILS_ENV = production and the rest is recommended since it avoids impacting production.
*3:As mentioned earlier, managing the site key through some mechanism rather than specifying it directly is recommended.
Adding reCAPTCHA v3 to Rails for Bot Protection
https://kenzo0107.github.io/en/2019/02/17/rails-recaptcha-v3-bot/