【Rails】Turbo環境でreCAPTCHA v2が正しく動作しない

に公開

結論

recaptcha_tagsメソッドにnoscript: falseを設定する

<%= recaptcha_tags noscript: false %>

問題

RailsアプリケーションでreCAPTCHA認証を実装した際に、チェック✅は押せているのに内部的に認証に失敗することがありました。
https://developers.google.com/recaptcha?hl=ja

recaptcha gemを導入すると、View側で以下のコードを書くだけでrecaptchaフォームが生成されます。(Controller側にも認証ロジックを書く必要はあります)

<%= recaptcha_tags %>

画面上で認証を行ってから送信しても内部で認証が失敗するのでリクエストパラメータを見ると、g-recaptcha-responseというreCAPTCHAのチェックを通過した時に入る認証トークンの値が空白になっていました。

"g-recaptcha-response" => "                "

そもそもチェックを押さずにリクエストを送信すると、値は空白ではなく空になります。

"g-recaptcha-response" => ""

g-recaptcha-responseに値が入る時と空白になる時があるので、それぞれに対してrecaptcha_tagsメソッドが生成しているHTMLを開発者ツールで確認して比較してみます。
recaptcha_tagsメソッドはデフォルトで通常のフォームに加えてnoscriptタグで囲ったフォームを生成します。

成功する場合はnoscriptタグの値が文字列で表示されています。

<noscript>
  "<div>
    <div style="width: 302px; height: 422px; position: relative;">
      <div style="width: 302px; height: 422px; position: absolute;">
        <iframe
          src="https://www.recaptcha.net/recaptcha/api/fallback?k=api-key"
          name="ReCAPTCHA"
          style="width: 302px; height: 422px; border-style: none; border: 0; overflow: hidden;">
        </iframe>
      </div>
    </div>
    <div style="width: 300px; height: 60px; border-style: none;
      bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px;
      background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
      <textarea name="g-recaptcha-response"
        class="g-recaptcha-response"
        style="width: 250px; height: 40px; border: 1px solid #c1c1c1;
        margin: 10px 25px; padding: 0px; resize: none;">
      </textarea>
    </div>
  </div>"
</noscript>

対して失敗するときは、noscriptタグの値が文字列ではなくHTMLとして解釈されています。

<noscript>
  <div>
    <div style="width: 302px; height: 422px; position: relative;">
      <div style="width: 302px; height: 422px; position: absolute;">
        <iframe
          src="https://www.recaptcha.net/recaptcha/api/fallback?k=api-key"
          name="ReCAPTCHA"
          style="width: 302px; height: 422px; border-style: none; border: 0; overflow: hidden;">
        </iframe>
      </div>
    </div>
    <div style="width: 300px; height: 60px; border-style: none;
      bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px;
      background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
      <textarea name="g-recaptcha-response"
        class="g-recaptcha-response"
        style="width: 250px; height: 40px; border: 1px solid #c1c1c1;
        margin: 10px 25px; padding: 0px; resize: none;">
      </textarea>
    </div>
  </div>
</noscript>

noscriptタグの内部がHTMLで解釈されてしまうとg-recaptcha-responseクラスを持つフォームが2つになってしまいます。この問題が不具合に影響していそうです。

対策

recaptcha_tagsメソッドにはnoscriptオプションがあります。
https://github.com/ambethia/recaptcha?tab=readme-ov-file#recaptcha_tags

falseに設定することで、noscriptタグの生成を行わないようにすることができます。

<%= recaptcha_tags noscript: false %>

結果、g-recaptcha-responseに値が入り正しく動作するようになりました。

まとめ

なぜnoscriptタグの生成に違いが出るのか、根本的な原因は分かりませんでした。
Turboを使用していないアプリケーションでは再現しなかったため、Turboが影響していそうです。

noscriptタグが必要なアプリケーション(JavaScriptが使えない/無効化している)でなければ、noscriptタグを生成しない対応で問題ないと思います。

環境

Ruby 3.4.1
Rails 8.0.1
turbo-rails 2.0.11
recaptcha 5.19.0

参考記事

https://github.com/ambethia/recaptcha/issues/407

ラグザイア

Discussion