/new_google_recaptcha

reCAPTCHA v3 Ruby on Rails gem

Primary LanguageRubyMIT LicenseMIT

Google Recaptcha v3 + Rails

Build Status RailsJazz https://www.patreon.com/igorkasyanchuk Listed on OpenSource-Heroes.com

Integrate Google Recaptcha v3 with Rails app.

Google Recaptcha console: https://www.google.com/recaptcha/admin#list

Recaptcha v3 documentation: https://developers.google.com/recaptcha/docs/v3

Usage

  • Open https://www.google.com/recaptcha/admin#list
  • register a new site
  • copy site_key and secret_key and put into config/initializers/new_google_recaptcha.rb
  • optionally, change the minimum_score in the initializer to a preferred float value (from 0.0 to 1.0)
  • in layout:
    <head>
      ...
      <%= yield :recaptcha_js %>
    </head>
  • in view where you for example you have a form:
    <%= content_for :recaptcha_js do %>
      <%= include_recaptcha_js %>
    <% end %>
    <form ...>
      <%#= 'checkout' is action name to be verified later %>
      <%= recaptcha_action('checkout') %>
    </form>
  • in controller:
    def create
      @post = Post.new(post_params)
      if NewGoogleRecaptcha.human?(
          params[:new_google_recaptcha_token],
          "checkout",
          NewGoogleRecaptcha.minimum_score,
          @post
        ) && @post.save
        redirect_to @post, notice: 'Post was successfully created.'
      else
        render :new
      end
    end
    
    
    # or
    # if you need to capture a humanity `score` from Google
    # before you need to add a column for example `humanity_score` (type: float) where this score will be saved.
    
    
    def create
      @post = Post.new(post_params)
      humanity_details =
        NewGoogleRecaptcha.get_humanity_detailed(
          params[:new_google_recaptcha_token],
          "checkout",
          NewGoogleRecaptcha.minimum_score,
          @post
        )
    
      @post.humanity_score = humanity_details[:score]
    
      if humanity_details[:is_human] && @post.save
        redirect_to @post, notice: 'Post was successfully created.'
      else
        render :new
      end
    end

There are two mandatory arguments for human? method:

  • token - token valid for your site
  • action - the action name for this request (the gem checks if it is the same as the name used with the token, otherwise a hacker could replace it on frontend to some another action used, but with lower score requirement and thus pass the verification)

You can verify recaptcha without using these arguments:

  • minimum_score - defaults to value set in the initializer (reCAPTCHA recommends using 0.5 as default)
  • model - defaults to nil which will result in not adding an error to model; any custom failure handling is applicable here

like this:

  NewGoogleRecaptcha.human?(params[:new_google_recaptcha_token], "checkout")

Saving humanity score from Google in your model

get_humanity_detailed method acts like human? method, the only difference is that it returns following hash with three key-value pairs:

  • is_human - whether actor is a human or not (same as result of human? method)
  • score - actual humanity score from recaptcha response
  • model - model which you trying to save

It could be handy if you want to store score in db or put it into logs or smth else. Real example is above in the code samples.

Add to your navigation links data-turbolinks="false" to make it works with turbolinks.

Installation

gem 'new_google_recaptcha'

And then execute:

$ bundle

And then run:

$ rails generate new_google_recaptcha initializer

And edit new_google_recaptcha.rb and enter your site_key and secret_key.

API

NewGoogleRecaptcha.human?(token, model) or NewGoogleRecaptcha.get_humanity_detailed(token, model) in contoller

  • token is received from google, must be sent to backend
  • model optional parameter. if you want to add error to model.

<%= include_recaptcha_js %> in layout (by using yield)

Include Google Recaptcha v3 JS into your Rails app. In head, right before </head>.

<%= recaptcha_action(action_name) %> in view

Action where recaptcha action was executed. Actions could be viewed in Admin console. More docs: https://developers.google.com/recaptcha/docs/v3. Action name could be "comments", "checkout", etc. Put any name and check scores in console.

How to add to the Devise

Generate Devise controllers and views, and edit "create" method.

class Users::RegistrationsController < Devise::RegistrationsController
...
  def create
    build_resource(sign_up_params)

    NewGoogleRecaptcha.human?(
      params[:new_google_recaptcha_token],
      "user",
      NewGoogleRecaptcha.minimum_score,
      resource) && resource.save

    yield resource if block_given?
    if resource.persisted?
      if resource.active_for_authentication?
        set_flash_message! :notice, :signed_up
        sign_up(resource_name, resource)
        respond_with resource, location: after_sign_up_path_for(resource)
      else
        set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
        expire_data_after_sign_in!
        respond_with resource, location: after_inactive_sign_up_path_for(resource)
      end
    else
      clean_up_passwords resource
      set_minimum_password_length
      respond_with resource
    end
  end

How to use in test or specs

At the end of the spec/rails_helper.rb put:

module NewGoogleRecaptcha
  def self.human?(*attrs)
    true
  end
end

Tests are located in specs folder and test/dummy/tests folder.

I18n support

reCAPTCHA passes one types of error explanation to a linked model. It will use the I18n gem to translate the default error message if I18n is available. To customize the messages to your locale, add these keys to your I18n backend:

new_google_recaptcha.errors.verification_human error message displayed when it is something like a robot, or a suspicious action

Also you can translate API response errors to human friendly by adding translations to the locale (config/locales/en.yml):

en:
  new_google_recaptcha:
    errors:
      verification_human: 'Fail'

Badge position

NewGoogleRecaptcha allows you to render badge in different positions

    <%= include_recaptcha_js badge: "bottomleft" %>

Three of existing badge values are bottomright, bottomleft and inline. 'inline' lets you position it with CSS.

TODO

  • check everything works with turbolinks
  • allow custom ID for input
  • more tests
  • handle exceptions with timeouts, json is not parsed
  • add support for non-Rails apps
  • add support for older Rails (should be easy since code is very simple)

Contributors

You are welcome to contribute.

License

The gem is available as open source under the terms of the MIT License.