reCAPTCHA Invisible 1画面で複数のrecaptchaがある時に別々のcallbackを実行

## ボットからサイトを守るための仕組みを提供してくれる、googleのサービス、reCAPTCHAについて調べたのでメモ。

### 今回調べたのは、reCAPTCHAのinvisible。1画面で複数のrecaptchaが必要なケースをrailsで実装します。※画面側については、基本的にはjavascriptなので言語関係なく使える内容になっていると思います。

# 導入手順
公式サイトを参考。キーを発行するだけ。数分で終わります。
「sign up for an API key pair」のリンクからキーを発行
https://developers.google.com/recaptcha/intro

# 実装方法
3つあります。
https://developers.google.com/recaptcha/docs/invisible
・Automatically bind the challenge to a button
・Programmatically bind the challenge to a button
・Programmatically invoke the challenge.

今回は、「Programmatically invoke the challenge」でやっています。
理由は、1画面で複数のrecaptchaが必要な時でも対応できるようにするため。
詳細は以下をみていただければわかるかもしれません。

# recaptchaをverify(検証)するまでの流れ
1.　画面にrecaptchaを埋め込む
2.　google側にリクエストを送信。(grecaptcha.execute)
3.　2.のレスポンスを受け取る。レスポンスの中のtokenが、```g-recaptcha-response```という画面フィールドにセットされる。
4.　```g-recaptcha-response```にセットされているtokenを検証してボットかどうか判断。


# 実装
## View
```html:view

<! --
// 1画面にformAとformBがある場合
// formAとformBにそれぞれあるSubmitボタン(POST)で別々の処理をさせたい
-->

<form id="form-a">
<div class="g-recaptcha" data-callback="formASubmit"></div>
<button type="button" onclick="grecaptcha.execute($('#form-a').find('.g-recaptcha').data('recaptcha-widget-id'));">A_OK</button>
</form>

<form id="form-b">
<div class="g-recaptcha" data-callback="formBSubmit"></div>
<button type="button" onclick="grecaptcha.execute($('#form-b').find('.g-recaptcha').data('recaptcha-widget-id'));">B_OK</button>
</form>

<!-- callback関数を定義 -->
<script type="text/javascript">
// grecaptcha.executeを実行すると対応するcallbackが呼ばれる
// recaptcha callback formA
    function formASubmit(token) {
            $('#form-a').submit();
    }

// recaptcha callback formB
    function formBSubmit(token) {
            $('#form-b').submit();
    }
</script>

 <!--
// 以下に記載の箇所は、後に記載するgemを使用する場合、invisible_recaptcha_tagsをオーバーライドして定義しても思います。
// その場合 <%= invisible_recaptcha_tags %> とView側で定義するだけでOKです。
-->
<!-- recaptchaをレンダリング -->
<script src="https://www.google.com/recaptcha/api.js?onload=recaptchaCallback&render=explicit" async defer></script>
<script type="text/javascript">
    var captchaWidgetIds = [];
    var recaptchaCallback = function() {
        // g-recaptchaクラスの箇所にrenderします。
        $('.g-recaptcha').each(function(index, el) {
            // sitekeyはgoogle側で発行したものを指定
            // badgeはrecaptchaのロゴを表示する位置
            // callbackはinvisible recaptchaでは必須。 $(el)としているのは、1画面で複数recaptchaがあり、別名のcallbackを呼べるようにするため
            var widgetId = grecaptcha.render(el, {'sitekey' :'xxxxxxxx','size' : 'invisible','badge' : 'inline', 'callback' : $(el).data('callback')});
            $(el).data('recaptcha-widget-id', widgetId);
            captchaWidgetIds.push(widgetId);
        });
    };
</script>

```

### 画面からGETする時の場合
```js:フォームからpostではなくgetする時
    // recaptcha callback
    function onSubmit(token) {
        window.location.href = "http://test" + "?g-recaptcha-response=" + token;
    }
```


# Controller
gemを使う
https://github.com/ambethia/recaptcha

```ruby:controller
 def verifyRecaptchaToken
    @model = Model.new

    # gemが提供しているメソッド。
    # tokenを検証し問題があれば@modelにセットします
    verify_recaptcha(model: @model)

    # @modelをみてverify結果を確認
    if @model.errors.empty?
       return true
    else
       return false
    end
  end
```

```ruby:参考_上記の処理をgemを使わず頑張って書くと多分こんな感じ・・・
  def verifyRecaptchaToken
    token = params[:"g-recaptcha-response"]
  # googleに問い合わせた結果のtokenを検証する
  # @return [Boolean] verifyが成功すればtrue、そうで無いならfalse
    url = "https://www.google.com/recaptcha/api/siteverify"
    response =Net::HTTP.post_form URI(url),
                             {"secret":"xxxxxx", "response":token}
    if response.code == '200'
      result = JSON.parse(response.body)['success']
    else
      result = false
    end
    return result
  end
```

以上です。
シンプルな作りであれば実装方法で挙げた３つの方法のうち、他の2つの方法が簡単です。
ただし1画面で複数recaptchaを使用したい場合は、今回記載した方法にしておくと良いと思います。