🗯️

Cloudflare TurnstileをRails/Reactでライブラリを使って動かす

に公開

Cloudflare Turnstile

CAPTCHA の cloudfalre 版
https://www.cloudflare.com/ja-jp/application-services/products/turnstile/

Cloudflare Turnstileとはなんぞや?をより詳細な説明
https://zenn.dev/kameoncloud/articles/cdf8f67bd8ce6f

Rails/Reactで動かしてみる

React

Reactでは、react-turnstileを利用します
https://github.com/marsidev/react-turnstile/tree/main

Code

import { useCallback, useId, useState } from "react";
import Turnstile from "react-turnstile";

const App = () => {
  const id = useId();
  const [email, setEmail] = useState("");
  const [token, setToken] = useState<string | null>(null);

  // CAPTCHA完了時に取得したトークンを状態に保存
  const handleCloudflareTurnstileTokenChange = (value: string | null) => {
    setToken(value);
  };

  const handleSubmit = useCallback(
    async (e: React.FormEvent<HTMLFormElement>) => {
      e.preventDefault();

      if (!token) {
        alert("CAPTCHAを完了してください");
        return;
      }

      // フォームの入力とCAPTCHAトークンをサーバーに送信
      const res = await fetch("/api/test_turnstile", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          email,
          "cf-turnstile-response": token, // RailsのGem:rails-cloudflare-turnstileが params[:cf-turnstile-response]を参照しているので、そのまま送る
        }),
      });

      if (res.ok) {
        alert("送信完了!");
      } else {
        alert("送信に失敗しました");
      }
    },
    [email, token]
  );

  return (
    <main>
      <section>
        <h1>Cloudflare Turnstile テスト</h1>
        <form onSubmit={handleSubmit}>
          {/* Emailの入力欄 */}
          <div>
            <label htmlFor={`${id}-email`}>Email:</label>
            <input
              id={`${id}-email`}
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              required
            />
          </div>

          {/* CAPTCHAウィジェット */}
          <Turnstile
            sitekey="YOUR_SITE_KEY"
            onSuccess={handleCloudflareTurnstileTokenChange} //setTokenでもOK
          />

          <button type="submit">送信</button>
        </form>
      </section>
    </main>
  );
};

export default App;

Rails

Railsは rails_cloudflare_turnstile を使用します
https://github.com/instrumentl/rails-cloudflare-turnstile

導入方法はrails_cloudflare_turnstileのRepositoryに書いてあるように

  1. Gemfileに gem 'rails_cloudflare_turnstile' を追加
  2. config/initializers/cloudflare_turnstile.rbを作成し、以下のような設定を追加
RailsCloudflareTurnstile.configure do |c|
  c.site_key = "XXXXXX"
  c.secret_key = "XXXXXXXX"
  c.fail_open = true
end
  1. 利用Controllerに、validate_cloudflare_turnstile をbeforeに追加する

Code

  class TestController < ApiController
    rescue_from RailsCloudflareTurnstile::Forbidden, with: :cloudflare_turnstile_forbidden_error
    before_action :validate_cloudflare_turnstile, only: [:create]

    # Cloudflare Turnstileの認証に失敗した場合
    def cloudflare_turnstile_forbidden_error
      render status: :forbidden,
          json: {
            id: 'forbidden',
            message: 'お手数ですが、Cloudflare Turnstileの認証を行い、再度送信してください。',
          }
    end
  end

validate_cloudflare_turnstile のメソッドがRequestの何を見てCloudflare Turnstileの成功・失敗を見ているか調べてみると
https://github.com/instrumentl/rails-cloudflare-turnstile/blob/7a492d0a361b8fc76d0d6a127e2f60f54c20a375/lib/rails_cloudflare_turnstile/controller_helpers.rb#L15

        body = {
          secret: config.secret_key,
          response: params["cf-turnstile-response"],
          remoteip: request.remote_ip
        }

となっており、paramsの["cf-turnstile-response"]を参照して、Cloudflare Turnstileの成功・失敗を判別していました。
そのため、Reactでは、Cloudflare Turnstileを扱っている箇所のRequest時に cf-turnstile-response をParameterに入れるようにしています。

つぶやき

Parameterで cf-turnstile-response のようなケバブケースでのParameterはあんまり使ったことがなかったので、rails_cloudflare_turnstile側で受け取るParameterを任意で設定出来るようになってると良かったなと思いました...

Discussion