DEV Community

Adding reCaptcha v3 to a Rails app without a gem

Felice Forby on June 04, 2019

This article goes over how you can add Google reCaptcha v3 to a Rails app and use it to verify a form submission. I couldn't find any other how-to...
Collapse
 
storrence88 profile image
Steven Torrence

Did you run into any issues with Turbolinks doing it this way? I noticed the page with my form will only load the recaptcha if I refresh the page.

I tried wrapping the grecaptcha.ready function in a document.on(turbolinks:load) like this:

<script>
  $(document).on('turbolinks:load', function() {
      grecaptcha.ready(function() {
          grecaptcha.execute('#{ENV['RECAPTCHA_SITE_KEY']}', {action: '#{action}'}).then(function(token) {
            document.getElementById("#{id}").value = token;
          });
       });
   });
</script>
Enter fullscreen mode Exit fullscreen mode

but to no avail. Any suggestions?

Collapse
 
sammymhowe profile image
Samantha Howe

Hey did you ever figure this out? Running into Uncaught ReferenceError: grecaptcha is not defined and it's referencing a lot of Turbolinks links.

Collapse
 
storrence88 profile image
Steven Torrence

Hey! Sorry for the late reply. It's been awhile but I think I ended up disabling turbolinks for that particular page and the code ran fine after that.

Check this out: github.com/turbolinks/turbolinks#d...

Hope that helps!

Thread Thread
 
sammymhowe profile image
Samantha Howe

Ahh okay! I'll try that out, thank you so much!

Collapse
 
morinoko profile image
Felice Forby

Hey Steven, I did not have any issues with turbolinks doing it the way I wrote in the blog post. I unfortunately haven't tried it with a document.on(turbolinks:load) wrapper.

Collapse
 
rcdbeato profile image
Ricardo Beato Jr

The following line makes a GET request, right? Shouldn't it be a POST?

Net::HTTP.get_response(uri)

I'm sorry if the question doesn't make sense, I'm a newbie, feeling a bit lost...
Thanks again for the amazing post, though, very easy to follow!

Collapse
 
morinoko profile image
Felice Forby

Hmm, it does look like the Google documentation says to make a POST request...

Honestly, when I wrote this (and still now), I had a really hard time understanding how the ReCaptcha worked, so I had to reference some other articles. Those articles all used Net::HTTP.get_response(uri) to get back the verification response.

You could try to make it a POST request instead and see how it works. I haven't tried it myself yet!

Collapse
 
ben_stegeman_6497b156504d profile image
Ben Stegeman • Edited

I'm implementing a reCAPTCHA currently. I believe it is supposed to be a POST request.

In fact, it's a little insecure to pass the secret key (and the token to a lesser extent) via a GET request. The query parameters at the end of the URL are encrypted in transit (so long as HTTPS is used,) but they can still show up in server logs, etc.

Doesn't apply here, but those query parameters also show up in browser history! GET should never be used to transmit sensitive information over the web.

Collapse
 
benjp profile image
Ben

Hi Felice, great article!

I'm handling my forms with ajax (remote: true) and I'm struggling to figure out how I can reset the recaptcha after a successful post. Any help would be appreciated :)

Thanks,
Ben

Collapse
 
morinoko profile image
Felice Forby

Hi Ben, thanks for reading!

Unfortunately, I've never done the resetting with ajax myself, so I can't give you any tips from my own experience. I was just checking out some other blogs and the google documentation, though, and you might be able to use the reCaptcha javascript api and reset it with grecaptcha.reset(widgetId);. This blog post mentions it but not sure if it works.

Collapse
 
jeremylopez profile image
Jeremy Lopez

I know this is a little late but I just had to solve this problem so hopefully my response can help someone else. Basically, since my form is a create action, I edited my create.js file to update the token on failed submission. Here's how:

Here's a portion of my form where I render a partial that include my captcha tag:

...

<div class="col-12">
  <div class="form-group captcha captcha-lead_creation_modal">
    <%= render "potential_clients/captcha_field", action: 'lead_creation_modal' %>
  </div>
</div>

<div class="col-lg-12 d-flex m-t-20">
  <%= f.submit 'Request More Information', class: "btn btn-md btn-block btn-info-gradiant" %>
</div>

Here's the complete partial: potential_clients/captcha_field:

<%= recaptcha_execute(action) %>

Finally, in my create.js I do the following:

// other error handling / success code goes here

$(".captcha-lead_creation_modal").html("<%= j render 'potential_clients/captcha_field', action: 'lead_creation_modal' %>")

This will re-render the portion of the form with the token and since it hits the helper function, a new token is generated!

Collapse
 
morinoko profile image
Felice Forby

Nice! Thank you so much for sharing your solution!

Collapse
 
letalone profile image
let-alone

First of all
thanks!! for helpful article for google reCaprcha v3 for rails..

I need some help to apply of this document...

rails version of my system is 4.1.11 ( because app which I use, can run under rails 4.xx only)

So I cannot use credentials.yml.enc file

Instead of credentials.yml.enc I should use another secret file (for example , secret_key_base )

But I do not know how to change the line to make the program work normally in ApplicationController.

I'm sorry, please teach me.

Collapse
 
morinoko profile image
Felice Forby

Hi there! Thank for reading!

Well, secrets from Rails' credential system are read with code that look like this (for example): Rails.application.credentials.dig(:recaptcha_secret_key). So, anywhere you see Rails.applications.credentials..., you need to replace it with how the other secret file like secret_key_base reads those variables. I'm not exactly sure how to do it for Rails 4 though, so you would need to research about that.

I have also used the dotenv gem before, which is very easy to use. You learn more about dotenv here. Basically, you make a file named .env and register any secret keys you need there. Then read them with something like ENV['SECRET_KEY'].

Hope that helps a little bit >< Sorry I don't know enough about the Rails 4 way to do it!

Collapse
 
cbillen profile image
Christian Billen

Anyone noticing recaptcha failures with this approach? the problem is a reCaptcha v3 will time out after 2 minutes, so if your users take longer to fill in the form the recaptcha json will return error-codes timeout-or-duplicate with a score of 0. It might be better to call recaptcha_execute on form submit to avoid this problem.

Collapse
 
jasonrhalpern profile image
jasonrhalpern

This article was really helpful so thanks for writing it up.

To address this comment, when I get back error-codes timeout-or-duplicate I am not receiving any score in that response (and success is false). I only get a score in the response when success is set to true, so I think the fact that he put json['success'] && json['score'] will address this correctly.

One thing I did was to add error handling with a begin/rescue in verify_recaptcha? because there are a couple things in that method that can blow up.

Collapse
 
nacengineer profile image
David Southard

Great article as I found the google Docs extremely lacking on how to set this up for any site much less a Rails site. They make a lot of assumptions that you'll just get the part they're completely glossing over, e.g. the hidden field and token assignment.

One mistake I think I found though is in the method recaptcha_execute you have a reference to RECAPTCHA_SITE_KEY which I'm guessing is actually supposed to be ENV['RECAPTCHA_SITE_KEY'] or a defined constant. This might trip up some people. :)

Collapse
 
morinoko profile image
Felice Forby

Yikes, nice catch! It was supposed to be a constant above the two methods. Now fixed! Thanks for pointing that out.

I had a hard time with the Google documentation too, which is a big reason why I wrote this post! Thank you for reading :D

Collapse
 
cardyard profile image
Mike Hayman

Top drawer Felice, thanks so much!

Collapse
 
rcdbeato profile image
Ricardo Beato Jr

Great post, simple to follow!