reCAPTCHA v3 on Rails

I implemented reCAPTCHA v3 on a Rails site recently. It was very straightforward, and I’m generally pleased with the outcome. Interestingly, the vast majority of spam requests seem to be made without having generated a reCAPTCHA token, suggesting that they are not even loading JavaScript. This points to a possible poor man’s approach for spam suppression: generate your CSRF token using JavaScript rather than emitting it directly in the form.

For better or worse, it is difficult to operate on the web today without JavaScript.

Version 3 of reCAPTCHA operates by capturing all user site activity, which is a privacy concern, but also allows it to function unobtrusively.

I defined several variables in my environment:


I load the reCAPTCHA script on secondary pages of my signup site, but not the root page and not any internal pages:

  <script src="<%= RECAPTCHA_SITE_KEY %>"></script>
<% end -%>

Then I attach an action to the signup form submission to generate the reCAPTCHA token and include it in the form data (the form is named signup_form and has a hidden input named recaptcha):

  // Add reCAPTCHA token on form submission
  $(function() {
    $('#signup_form').submit(function(event) {
      grecaptcha.ready(function() {
        grecaptcha.execute('<%= RECAPTCHA_SITE_KEY %>', {action: 'submit'}).then(function(token) {
<% end -%>

This site already has several conditions for trashing a suspicious signup request, with a simple error page that directs visitors to reach out to our administrators by email. I added a new condition to check the reCAPTCHA token:

  # Check reCAPTCHA
  recap_uri = URI.parse('')
  recap_params = { secret: RECAPTCHA_SECRET_KEY, response: params[:recaptcha] }
  response = Net::HTTP.post_form(recap_uri, recap_params) "Recaptcha result: " + response.code + " / " + response.body
  response_json = JSON.load(response.body) if response.code == "200"

# Silently throttle requests that fail reCAPTCHA
if RECAPTCHA_CHECK && (response.code != "200" || !response_json['success'] || response_json['score'] < 0.5)
  Mailer.exception_notification("reCAPTCHA throttled signup"), params).deliver_now
  redirect_to :action => :thankyou

As you can see, for now I am operating with a threshold of 0.5, but I may adjust this over time. In my own testing, I generated a confidence value of 0.9.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s