<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Francois Adrien</title>
    <description>The latest articles on DEV Community by Francois Adrien (@fadrien).</description>
    <link>https://dev.to/fadrien</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F644693%2F138fb9ee-6dc5-409e-8176-40ef14126715.jpeg</url>
      <title>DEV Community: Francois Adrien</title>
      <link>https://dev.to/fadrien</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fadrien"/>
    <language>en</language>
    <item>
      <title>Rails Devise and ReCaptcha with Hotwire (Turbo and Stimulus)</title>
      <dc:creator>Francois Adrien</dc:creator>
      <pubDate>Thu, 10 Jun 2021 13:37:50 +0000</pubDate>
      <link>https://dev.to/fadrien/rails-devise-and-recaptcha-with-hotwire-turbo-and-stimulus-2hoh</link>
      <guid>https://dev.to/fadrien/rails-devise-and-recaptcha-with-hotwire-turbo-and-stimulus-2hoh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Last month, I decided to install &lt;a href="https://hotwire.dev/"&gt;Hotwire&lt;/a&gt; &lt;em&gt;(which is the combination of &lt;a href="https://turbo.hotwire.dev/"&gt;Turbo&lt;/a&gt; and &lt;a href="https://stimulus.hotwire.dev/"&gt;Stimulus&lt;/a&gt;)&lt;/em&gt; to my &lt;strong&gt;Rails&lt;/strong&gt; app. I thought it would be easy and quick to make those upgrades but I kind of underestimated the task. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stimulus&lt;/strong&gt; is really easy to learn if you already know javascript. &lt;strong&gt;Turbo&lt;/strong&gt; is more abstract and can be difficult to fully understand how it works under the hood. In addition, &lt;strong&gt;Turbo&lt;/strong&gt; is quite new &lt;em&gt;(it is the evolution of &lt;a href="https://github.com/turbolinks/turbolinks"&gt;Turbolinks&lt;/a&gt;)&lt;/em&gt; so it is not really well documented as of June 2021.&lt;/p&gt;

&lt;p&gt;One of the challenge I had to face is to make &lt;strong&gt;Turbo&lt;/strong&gt; work with &lt;a href="https://github.com/heartcombo/devise"&gt;Devise&lt;/a&gt; and to keep my &lt;a href="https://github.com/ambethia/recaptcha"&gt;ReCaptcha&lt;/a&gt; strategy working &lt;em&gt;(First verify ReCaptcha v3 and then display ReCaptcha v2 if v3 failed)&lt;/em&gt;. So let's dive in !&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For the following, I assume you have a functioning Rails app with the following Gems installed: Devise, Stimulus-rails, turbo-rails and recaptcha&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Devise error messages
&lt;/h2&gt;

&lt;p&gt;If you installed Turbo successfully, then you should have noticed that when submitting your &lt;strong&gt;Log in&lt;/strong&gt; form it now renders as &lt;code&gt;TURBO_STREAM&lt;/code&gt; format &lt;em&gt;(this is because Turbo applies to forms and not only to links like Turbolinks did)&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Processing by PagesController#home as TURBO_STREAM

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you submit your &lt;strong&gt;Log in&lt;/strong&gt; form with the correct information, everything should work as expected.&lt;/p&gt;

&lt;p&gt;The problem is when you do not enter the correct information &lt;em&gt;(unknown email address, wrong password...)&lt;/em&gt;, the error messages do not appear anymore and this is not what we want.&lt;br&gt;
For fixing this part, I was largely inspired by this excellent &lt;a href="https://gorails.com/episodes/devise-hotwire-turbo"&gt;tutorial&lt;/a&gt; from Chris Oliver.&lt;/p&gt;
&lt;h3&gt;
  
  
  1 - Add Turbo Stream to devise formats
&lt;/h3&gt;

&lt;p&gt;First we need to add Turbo Stream into devise navigational formats list. In the &lt;em&gt;devise.rb&lt;/em&gt; file, uncomment the &lt;code&gt;config.navigational_formats&lt;/code&gt; line and add the &lt;code&gt;turbo_stream&lt;/code&gt; format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# devise.rb&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigational_formats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'*/*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:turbo_stream&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2 - Override Devise Respond method
&lt;/h3&gt;

&lt;p&gt;In order for devise to display errors to the user when the authentication failed, we need to partially override the &lt;strong&gt;respond&lt;/strong&gt; method of the &lt;code&gt;Devise::FailureApp&lt;/code&gt; class by creating our own &lt;code&gt;TurboFailureApp&lt;/code&gt;. This will "treat" the turbo stream like it would for an HTML request by redirecting back to the &lt;strong&gt;Log in&lt;/strong&gt; page in case of failure. You can add this class directly on top of the &lt;em&gt;devise.rb&lt;/em&gt; file (out of the &lt;code&gt;devise.config&lt;/code&gt; block) since we will probably never use this new class in another context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# devise.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TurboFailureApp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FailureApp&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;respond&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request_format&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:turbo_stream&lt;/span&gt;
      &lt;span class="n"&gt;redirect&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;skip_format?&lt;/span&gt;
    &lt;span class="sx"&gt;%w[html turbo_stream */*]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt; &lt;span class="n"&gt;request_format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3 - Configure Warden
&lt;/h3&gt;

&lt;p&gt;Finally, we need to tell &lt;a href="https://github.com/wardencommunity/warden/wiki/Overview"&gt;Warden&lt;/a&gt; (The Rack-based middleware used by devise for authentication) to use our newly created class as the failure manager. In the &lt;em&gt;devise.rb&lt;/em&gt;, uncomment the &lt;code&gt;config.warden&lt;/code&gt; block and replace the inner content like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# devise.rb&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warden&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TurboFailureApp&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it, you can try it out and should see Devise error messages appear on authentication failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling ReCaptchas with Turbo and Stimulus
&lt;/h2&gt;

&lt;p&gt;Now that devise authentication works correctly with &lt;strong&gt;Turbo&lt;/strong&gt;, we want to implement a &lt;strong&gt;ReCaptcha&lt;/strong&gt; protection to our form. The &lt;strong&gt;ReCaptcha&lt;/strong&gt; strategy I use is quite classical as it consists of first rendering a &lt;strong&gt;ReCaptcha v3&lt;/strong&gt; (which is invisible and based on requests liability scores), and in case the score is lower than what is expected, we render a &lt;strong&gt;ReCaptcha v2&lt;/strong&gt; Checkbox so that the user can check it manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  1 - Add the ReCaptcha V3 in your Log in form
&lt;/h3&gt;

&lt;p&gt;Thanks to the &lt;em&gt;recaptcha&lt;/em&gt; Gem we have helpers method to simplify the process. We will also play along with &lt;strong&gt;Turbo Frames&lt;/strong&gt; to handle html replacement when ReCaptcha v3 is not correctly verified. In our &lt;em&gt;sessions/new.html.erb&lt;/em&gt; file we can include the following code within the &lt;strong&gt;Log in&lt;/strong&gt; form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- devise/sessions/new.html.erb --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s1"&gt;'recaptchas'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;recaptcha_v3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;site_key: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RECAPTCHA_SITE_KEY_V3'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2 - Implement a method to verify the ReCaptchas
&lt;/h3&gt;

&lt;p&gt;In our &lt;strong&gt;Sessions controller&lt;/strong&gt;, we need to implement a method that will allow the &lt;strong&gt;ReCaptcha API&lt;/strong&gt; to verify the request. We will use a &lt;code&gt;prepend_before_action&lt;/code&gt; callback on the create method, to verify the ReCaptchas requests even before devise user authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# users/sessions_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Users::SessionsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SessionsController&lt;/span&gt;
  &lt;span class="n"&gt;prepend_before_action&lt;/span&gt; &lt;span class="ss"&gt;:validate_recaptchas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="kp"&gt;protected&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_recaptchas&lt;/span&gt;
    &lt;span class="n"&gt;v3_verify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;verify_recaptcha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                 &lt;span class="ss"&gt;minimum_score: &lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                 &lt;span class="ss"&gt;secret_key: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RECAPTCHA_SECRET_KEY_V3'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v3_verify&lt;/span&gt;

    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resource_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="n"&gt;sign_in_params&lt;/span&gt;
    &lt;span class="n"&gt;respond_with_navigational&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above will work if the request score is 0.9 or higher, but if it is lower, it will render an error because &lt;strong&gt;Turbo&lt;/strong&gt; will look for a partial with the &lt;code&gt;turbo_stream&lt;/code&gt; format. Plus we are only telling devise to render the form again without any improvement. We want to render the &lt;strong&gt;ReCaptcha v2&lt;/strong&gt; so that the user is not stuck on the same form with a low request score again and again.&lt;/p&gt;

&lt;h3&gt;
  
  
  3 - Create a turbo_stream view for ReCaptcha v2
&lt;/h3&gt;

&lt;p&gt;Then let's create a view for our ReCaptcha v2 injection into the form. Since we want to replace the ReCaptcha v3 with the ReCaptcha v2 we will use the Turbo Stream helper &lt;code&gt;turbo_stream.replace&lt;/code&gt; to wrap the code we want to inject:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- sessions/new.turbo_stream.erb --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt; &lt;span class="ss"&gt;:recaptchas&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the &lt;code&gt;:recaptchas&lt;/code&gt; attribute is the same as the &lt;code&gt;turbo_frame_tag&lt;/code&gt; &lt;em&gt;id&lt;/em&gt; we set in our &lt;em&gt;sessions/new.html.erb&lt;/em&gt; as a wrapper for our ReCaptcha v3. We will replace the content of the &lt;code&gt;turbo_frame_tag 'recaptchas'&lt;/code&gt; with the content of our &lt;code&gt;turbo_stream&lt;/code&gt;. Cool right ?&lt;/p&gt;

&lt;p&gt;Now is the tricky part, you will probably be tempted to use the &lt;em&gt;recpatchas&lt;/em&gt; Gem helper method to render our ReCaptcha v2 checkbox like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- sessions/new.turbo_stream.erb --&amp;gt;&lt;/span&gt;
# Do not do that !

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt; &lt;span class="ss"&gt;:recaptchas&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mt-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;recaptcha_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;site_key: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RECAPTCHA_SITE_KEY_V2'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will not work because of &lt;strong&gt;Turbo&lt;/strong&gt;. Indeed, since Turbo avoid the page refresh and is not evaluating inline scripts when rendering (It is a bug tracked here: &lt;a href="https://github.com/hotwired/turbo/issues/186"&gt;hotwired/turbo#186&lt;/a&gt;), the &lt;strong&gt;ReCaptcha API&lt;/strong&gt; will not be called again. We thus have to render the &lt;strong&gt;ReCaptcha v2&lt;/strong&gt; explicitly and this is where &lt;strong&gt;Stimulus&lt;/strong&gt; comes in !&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;turbo_stream.replace&lt;/code&gt; block from your &lt;em&gt;new.turbo_stream.erb&lt;/em&gt; file, insert an empty DIV with the id &lt;code&gt;recaptchaV2&lt;/code&gt; and with the necessary attributes to be able to connect to our future Stimulus controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- sessions/new.turbo_stream.erb --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt; &lt;span class="ss"&gt;:recaptchas&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'mt-4'&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;'recaptchaV2'&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;'load-recaptcha-v2'&lt;/span&gt; &lt;span class="na"&gt;data-load-recaptcha-v2-site-key-value=&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RECAPTCHA_SITE_KEY_V2'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/div&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4 - Render ReCaptcha v2 explicitly from Stimulus
&lt;/h3&gt;

&lt;p&gt;Create a Stimulus controller called &lt;strong&gt;load_recaptcha_v2_controller.js&lt;/strong&gt; &lt;em&gt;(It has to be the same name as the data-controller attribute from our &lt;code&gt;recaptchaV2&lt;/code&gt; DIV)&lt;/em&gt;. In this controller we will fetch our ReCaptcha v2 &lt;strong&gt;sitekey&lt;/strong&gt; value defined in our &lt;code&gt;data-load-recaptcha-v2-site-key-value&lt;/code&gt; attribute from our &lt;code&gt;recaptchaV2&lt;/code&gt; DIV and then we will inject the &lt;strong&gt;ReCaptcha V2&lt;/strong&gt; explicitely on initialization. So your &lt;em&gt;load_recaptcha_v2&lt;/em&gt; controller should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// load_recaptcha_v2_controller.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stimulus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;siteKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;grecaptcha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recaptchaV2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sitekey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteKeyValue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5 - Add the ReCaptcha v2 verification
&lt;/h3&gt;

&lt;p&gt;We now need to add the &lt;strong&gt;ReCaptcha v2&lt;/strong&gt; verification in our &lt;strong&gt;Session controller&lt;/strong&gt;. To do so is really simple, just modify your &lt;code&gt;validate_recaptchas&lt;/code&gt; method to include v2 validation like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_recaptchas&lt;/span&gt;
    &lt;span class="n"&gt;v3_verify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;verify_recaptcha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                 &lt;span class="ss"&gt;minimum_score: &lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                 &lt;span class="ss"&gt;secret_key: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RECAPTCHA_SECRET_KEY_V3'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;v2_verify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;verify_recaptcha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;secret_key: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RECAPTCHA_SECRET_KEY_V2'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v3_verify&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;v2_verify&lt;/span&gt;

    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resource_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="n"&gt;sign_in_params&lt;/span&gt;
    &lt;span class="n"&gt;respond_with_navigational&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now test it ! To verify if the &lt;strong&gt;ReCaptcha v2&lt;/strong&gt; is correctly rendered, set the &lt;code&gt;minimum_score&lt;/code&gt; of the  ReCaptcha v3 verification helper to &lt;strong&gt;1&lt;/strong&gt; which will cause the v3 to fail everytime and this is what we want in order to test it. Of course do not forget to put this value back to normal afterwards !&lt;/p&gt;

&lt;h3&gt;
  
  
  6 - Configure Flash messages
&lt;/h3&gt;

&lt;p&gt;In case of &lt;strong&gt;ReCaptcha v3&lt;/strong&gt; validation failures, you probably want your user to know what is happening by displaying a &lt;strong&gt;flash message&lt;/strong&gt; like "Please check the ReCaptcha to continue" or something like this.&lt;/p&gt;

&lt;p&gt;Well, we need to make some small changes to the default flash messages settings because &lt;strong&gt;Flash messages&lt;/strong&gt; will not work when Turbo (again !) do not redirect.&lt;/p&gt;

&lt;p&gt;Go to your &lt;em&gt;application.html.erb&lt;/em&gt; file and wrap the Flash messages partial into a &lt;strong&gt;Turbo Frame&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- application.html.erb --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="ss"&gt;:flashes&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s1"&gt;'shared/flashes'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then all you have to do is &lt;strong&gt;update&lt;/strong&gt; the Flash messages partial through a &lt;strong&gt;Turbo Stream&lt;/strong&gt; in your &lt;em&gt;sessions/new.turbo_stream.erb&lt;/em&gt; file which now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt; &lt;span class="ss"&gt;:recaptchas&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'mt-4'&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;'load-recaptcha-v2'&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;'recaptchaV2'&lt;/span&gt; &lt;span class="na"&gt;data-load-recaptcha-v2-site-key-value=&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RECAPTCHA_SITE_KEY_V2'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/div&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:flashes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s1"&gt;'shared/flashes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;alert: &lt;/span&gt;&lt;span class="s1"&gt;'Please check the ReCaptcha to continue'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is finally it ! You should have a functional Devise authentication form working with Turbo. &lt;/p&gt;

&lt;p&gt;I hope this helped and of course if you have some doubts or even better, if you have improvements or refactoring to propose please feel free to contact me !&lt;/p&gt;

</description>
      <category>hotwire</category>
      <category>turbo</category>
      <category>stimulus</category>
      <category>recaptcha</category>
    </item>
  </channel>
</rss>
