-
Notifications
You must be signed in to change notification settings - Fork 441
Recaptcha with Turbo and Stimulus
If you would like to make Recaptcha work with Turbo and Stimulus, you'll have to perform some additional steps.
You'll need keys for both Recaptcha v3 and v2 so go ahead and grab those if you haven't already done so.
By default we'll use Recaptcha v3. It must be wrapped in a turbo frame (please note that your form can also be wrapped):
<%= turbo_frame_tag user, target: '_top' do %>
<%= form_with model: user do |f| %>
<!-- your form fields here... -->
<%= turbo_frame_tag 'recaptcha' do %>
<div class="mb-3 row">
<%= recaptcha_v3 action: 'signup', site_key: ENV['RECAPTCHA_KEY_V3'], turbolinks: true %>
</div>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
<% end %>
Notice the usage of the turbolinks
option which is mandatory in this setup.
Now let's perform verification:
def create
@user = User.new user_params
@user.validate # this line will validate the user even if Recaptcha failed. This way we will present all potential validation errors right away
check = verify_recaptcha action: 'signup', minimum_score: 0.7, secret_key: ENV['RECAPTCHA_SECRET_V3']
if check && @user.save
# everything is great, you can now let the user in and redirect them somewhere
else
render :new # if something goes wrong, we'll re-render the form
end
end
Please note that action
should be the same as the one provided in the view.
If Recaptcha v3 check failed, we are going to display a regular "I'm not a robot" checkbox using Recaptcha v2. Therefore, create a new view called new.turbo_stream.erb
:
<%= turbo_stream.replace 'recaptcha' do %>
<div class="mb-3 row"
id='recaptchaV2'
data-controller='recaptcha-v2'
data-recaptcha-v2-site-key-value="<%= ENV['RECAPTCHA_KEY'] %>"></div>
<% end %>
We are replacing the old recaptcha with a new one. Here you can also re-render the actual form thus displaying validation errors:
<%= turbo_stream.replace dom_id(@user), partial: 'users/form', locals: {user: @user} %>
<%= turbo_stream.replace 'recaptcha' do %>
<div class="mb-3 row"
id='recaptchaV2'
data-controller='recaptcha-v2'
data-recaptcha-v2-site-key-value="<%= ENV['RECAPTCHA_KEY'] %>"></div>
<% end %>
You can also display flash messages here and perform other actions as needed.
At this point we are simply displaying a placeholder for our new captcha. It will be processed by Stimulus in the next step.
Now create a new Stimulus controller:
// recaptcha_v2_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static values = { siteKey: String }
initialize() {
grecaptcha.render("recaptchaV2", { sitekey: this.siteKeyValue } )
}
}
At this point we actually render a new captcha. Don't forget to properly register your controller inside the index.js
file.
Now modify your controller:
def create
@user = User.new user_params
check = (verify_recaptcha action: 'signup', minimum_score: 0.7, secret_key: ENV['RECAPTCHA_SECRET_V3']) ||
(verify_recaptcha model: @user, secret_key: ENV['RECAPTCHA_SECRET'])
if check && @user.save
# Everything is good
else
@user.validate # add any other validation errors
# @user.validate generates a new errors, so that recaptcha error message cannot be seen.
@user.errors.add(:base, t('recaptcha.errors.verification_failed')) unless check
render :new
end
end
This guide is partially based on this tutorial at dev.to which explains how to get Recaptcha working with Devise (the overall approach is the same).