4

I am trying to setup Recaptcha in my rails 5 application as it's described in the documentation but it fails.

I use this gem: recaptcha (4.6.6), ruby 2.5.0 and rails 5.1.4

In view form:

<%= flash[:recaptcha_error] %>
<%= recaptcha_tags %>

In devise registrations controller:

  prepend_before_action :check_captcha, only: :create

  private

  def check_captcha
    unless verify_recaptcha
      self.resource = resource_class.new sign_up_params
      resource.validate # Look for any other validation errors besides Recaptcha
      respond_with_navigational(resource) { redirect_to new_user_registration_path }
    end 
  end

In my initializers/recaptcha.rb

Recaptcha.configure do |config|
  config.site_key   = Rails.application.config_for(:recaptcha)['site_key']
  config.secret_key = Rails.application.config_for(:recaptcha)['secret_key']
end

In my recaptcha.yml:

default: &default
  site_key: <%= ENV["RECAPTCHA_SITE_KEY"] %>
  secret_key: <%= ENV["RECAPTCHA_SECRET_KEY"] %>

development:
  <<: *default

test:
  <<: *default

staging:
  <<: *default

production:
  <<: *default

In /etc/environments:

# RECAPTCHA
RECAPTCHA_SITE_KEY=6Lfg3ksUAAAAABOD_OXCtPO60*******
RECAPTCHA_SECRET_KEY=6Lfg3ksUAAAAAOmFGdAxdo8*******

PROBLEM

After adding ENV variables to /etc/environments, I exported it with this command:

for line in $( cat /etc/environment ) ; do export $line ; done

Then I check that Recaptcha module is configured correctly:

/home/deploy/apps/app_name/current$ bundle exec rails c
Loading staging environment (Rails 5.1.4) 
2.5.0 :001 > Recaptcha::Configuration.new
 => #<Recaptcha::Configuration:0x0000000006601908 @skip_verify_env=["test", "cucumber"], @handle_timeouts_gracefully=true, @secret_key="6Lfg3ksUAAAAAOmFGdAxdo8H*************", @site_key="6Lfg3ksUAAAAABOD_OXCtPO*************"> 
2.5.0 :002 > Recaptcha::Configuration.new.site_key!
 => "6Lfg3ksUAAAAABOD_OXCtPO*************" 

Also, I see these ENV variables when I run printenv command (so it's really loaded)

After that, I restarted rails and got an error

No site key specified.

/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/recaptcha-4.6.6/lib/recaptcha/configuration.rb:47:in `site_key!'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/recaptcha-4.6.6/lib/recaptcha/client_helper.rb:79:in `recaptcha_components'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/recaptcha-4.6.6/lib/recaptcha/client_helper.rb:15:in `recaptcha_tags'
/home/deploy/apps/app_name/releases/20180310222304/app/views/users/registrations/new.html.erb:27:in `block in _app_views_users_registrations_new_html_erb___216558772140569572_69973306795360'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/actionview-5.1.4/lib/action_view/helpers/capture_helper.rb:39:in `block in capture'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/actionview-5.1.4/lib/action_view/helpers/capture_helper.rb:203:in `with_output_buffer'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/actionview-5.1.4/lib/action_view/helpers/capture_helper.rb:39:in `capture'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/actionview-5.1.4/lib/action_view/helpers/form_helper.rb:450:in `form_for'
/home/deploy/apps/app_name/releases/20180310222304/app/views/users/registrations/new.html.erb:21:in `_app_views_users_registrations_new_html_erb___216558772140569572_69973306795360'
/home/deploy/apps/app_name/shared/bundle/ruby/2.5.0/gems/actionview-5.1.4/lib/action_view/template.rb:157:in `block in render'
bmalets
  • 3,207
  • 7
  • 35
  • 64
  • can you print you recaptcha key in any view? or controller? maybe rails do not load your keys. I recommendo use [figaro gem](https://github.com/laserlemon/figaro) – inye Mar 11 '18 at 11:47
  • oh... I added this line into view `

    site: <%= Recaptcha::Configuration.new.site_key %>

    `, and see only `site:` in browser. You are right, recaptcha env variables is not loaded into rails...
    – bmalets Mar 11 '18 at 11:56
  • I am confused that recaptha gem force me to use ENV variables, `dotenv` / `figaro` gems... – bmalets Mar 11 '18 at 12:01
  • ENV variables is to do not store important/secret/private data in your code and more important in your repository. ENV variables are only in your server. – inye Mar 11 '18 at 13:58
  • sure, but I should choose to use ENVs or not. According to Rails rules, gem's configurations should be loaded from rails initializers. Not directly from ENV variables [like here](https://github.com/ambethia/recaptcha/blob/master/lib/recaptcha/configuration.rb#L39) – bmalets Mar 11 '18 at 17:39

4 Answers4

7

I am posting here in case someone is looking for a Rails 5.2 solution to setting up Recaptcha keys. This solution utilizes the new config/master.key and config/credentials.yml.enc encryption file.

  1. Add the Recaptcha keys to the credentials.yml.enc file by editing the file in your local terminal:

    EDITOR="vim" rails credentials:edit

  2. After adding the keys to the credentials file (see example below), save and exit the file. Upon exit, the credentials.yml.enc file will then be automatically encrypted. The master.key is necessary for decryption by your application. Before encryption:

    recaptcha_site_key: 6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy recaptcha_secret_key: 6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxx


3. Create a file named config/recaptcha.rb in your Rails application and add the following code to it:
Recaptcha.configure do |config| config.site_key = Rails.application.credentials.dig(:recaptcha_site_key) config.secret_key = Rails.application.credentials.dig(:recaptcha_secret_key) end

This solution works locally and on Ubuntu/nginx in production. You won't need a gem or environment variables for it to work. If the master.key fails to decrypt, you may need to delete both the credentials.yml.enc file and possibly even the master.key file, then repeat this process locally (EDITOR="vim" rails credentials:edit, etc.) before copying over a new master.key to production and re-deploying.

user1846531
  • 71
  • 1
  • 3
  • 2
    Great solution, one tweak: ```config/recaptcha.rb``` --> ```config/initializers/recaptcha.rb``` – Lush Jun 05 '19 at 01:47
  • 1
    Another thing to keep in mind is that you will need to set your Master key in the environment variables so that it will run in production. For example, Heroku would be `heroku config:set RAILS_MASTER_KEY=123456789`. Otherwise it will error out with "no site key specified" because it couldn't decript it from your credentials file. – PhillipKregg Jun 21 '20 at 23:42
0

I still don't know what is the cause of the "No site key specified" error.

I really don't like gem 'recapthca' works directly with ENV variables,

and also I spent too much time on investigations.

So, I decided to not use this gem and write my own code.

I use only Invisible Recaptcha in my application.

Config file (loads secret and site keys)

# /config/recaptcha.yml
default: &default
  site_key: <%= ENV["RECAPTCHA_SITE_KEY"] %>
  secret_key: <%= ENV["RECAPTCHA_SECRET_KEY"] %>

development:
  <<: *default

test:
  <<: *default

staging:
  <<: *default

production:
  <<: *default

Application helper (a button with Recaptcha helper)

# /app/helpers/application_helper.rb
module ApplicationHelper
  def submit_with_recaptcha(text, custom_options)
    unless custom_options[:data].has_key?(:form_id) 
      raise "Data Form Id option not found ('{data: {form_id: 'id_without_dash'}')."
    end

    options = {
      type: 'button',
      data: {
        form_id: custom_options[:data][:form_id],
        sitekey: recaptcha_site_key,
        callback: "submit#{custom_options[:data][:form_id].camelize}#{Time.current.to_i}"
      },
      class: (custom_options[:class].split(' ') + ['g-recaptcha']).uniq.join(' ')
    }

    script_code = <<-SCRIPT
      function #{options[:data][:callback]}() {
        document.getElementById('#{options[:data][:form_id]}').submit();
      }
    SCRIPT

    javascript_tag(script_code) + content_tag(:div, class: 'recaptcha_wrapper'){ submit_tag(text, options) }
  end

  private

  def recaptcha_site_key
    Rails.application.config_for(:recaptcha)['site_key']
  end
end

Verification service (as it uses external API)

# app/services/google_recaptcha/verification.rb
module GoogleRecaptcha
  # https://developers.google.com/recaptcha/docs/verify
  class Verification
    # response - params['g-recaptcha-response'])
    def self.successful?(recaptcha_params, remoteip)
      verify_url = URI.parse('https://www.google.com/recaptcha/api/siteverify')
      verify_request = Net::HTTP::Post.new(verify_url.path)

      verify_request.set_form_data(
        response: recaptcha_params,
        secret: secret_key,
        remoteip: remoteip
      )

      connection = Net::HTTP.new(verify_url.host, verify_url.port)
      connection.use_ssl = true

      Rails.logger.info '[RECAPTCHA] Sending verification request.'

      verify_response = connection.start { |http| http.request(verify_request) }
      response_data = JSON.parse(verify_response.body)

      Rails.logger.info "[RECAPTCHA] Verification response is#{' not' unless response_data['success']} successful."

      response_data['success']
    end

    private

    def self.secret_key
      Rails.application.config_for(:recaptcha)['secret_key']
    end
  end
end

Controller Concern (Recaptcha verification in before_action)

# app/controllers/concerns/recaptchable.rb
module Recaptchable
  extend ActiveSupport::Concern

  included do
    before_action :verify_recaptcha, only: [:create]
  end

  private

  def verify_recaptcha
    unless GoogleRecaptcha::Verification.successful?(recaptcha_params['g-recaptcha-response'], request.remote_ip)
      render :new
      return
    end
  end

  def recaptcha_params
    params.permit(:'g-recaptcha-response')
  end
end

Usage

Add concern to your controller:

class MyController < ShopController
  include Recaptchable
end

Add www.google.com/recaptcha/api.js javascript to your page

Add submit_with_recaptcha helper into your form

<%= form_for @delivery, url: users_delivery_path, method: 'post' do |f| %>
  <%= submit_with_recaptcha t('order.deliver.to_confirmation'), data: {form_id: 'new_delivery'}, class: 'btn-round' %>

<% end %>
<%= javascript_include_tag "https://www.google.com/recaptcha/api.js?hl=#{I18n.locale}", 'data-turbolinks-track': 'reload' %>

That's it.

bmalets
  • 3,207
  • 7
  • 35
  • 64
0

Note: I'm posting this answer here for people who may find this question. Here is how I solved the problem.

I use local_env.yml for my environment variables. I just started using the gem and added RECAPTCHA_SITE_KEY & RECAPTCHA_SECRET_KEY to local_env.yml. I got the same error.

It took a bit to figure out that the gem directly used the variables. I ended up putting the following statements in ~/.bashrc similar to what the documentation said but without the quotes around the values.

export RECAPTCHA_SITE_KEY=6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy
export RECAPTCHA_SECRET_KEY=6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx

I host my applications on Heroku. I executed the following terminal commands to set my environment variables in Heroku.

heroku config:set RECAPTCHA_SITE_KEY=‘6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy’
heroku config:set RECAPTCHA_SECRET_KEY=‘6LcGuI4U6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxxAAAAGAWMYRKFGfHUCSD0SPrMX2lfyl9’
0

Are you using nginx? Nginx removes ENV vars (except TZ) and it seems that the recaptcha gem is particularly sensitive to this. From experience, when using the dotenv gem other ENV vars work ok, recaptcha ENV vars are ignored.

You can solve the issue by adding the env vars to the top of your nginx.conf.

env RECAPTCHA_SITE_KEY=value1;
env RECAPTCHA_SECRET_KEY=value2;

Here's nginx's documentation on the matter.

mvanio
  • 505
  • 3
  • 15