Google reCAPTCHA v3をRuby On Railsに入れてみました〜

# reCAPTCHAとは
ボットなどによる悪質なアクセスからWebサイトを守るための機能です。
V2については以下の記事を参考してください。
https://qiita.com/amagasu1234/items/9760c2c410776fd02e12

今回はRubyOnRails環境でreCAPTCHA V3を追加してみました。

|                  |       説明 |        |
|:-----------------|:------------------|:------------------:|
| V1               | 歪められた文字を読み取って入力することでボットではないことを確認する。             2018年3月サービス終了|![スクリーンショット 2019-12-21 22.26.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/6d23b3a4-a2e6-1306-c251-51d18aa8311d.png)|
| V2               | 画像を選択させることで人間とボットを判別する。|![スクリーンショット 2019-12-21 22.47.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/9410ee7b-b752-4125-cf07-bf588aaac7a0.png)|
| V3               | V1/V2とは違ってユーザー入力なし、アクセスに対する人間らしさのスコア値 (0.0 〜 1.0) でボット判別。 2018年10月29日サービス開始| ![スクリーンショット 2019-12-21 22.47.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/f252ac4b-cf86-a500-12fc-c5a53f620e4f.png)|


#開発環境
rails: 5.1
macOS Mojave
IDE: RubyMine
今回のテストソースは以下のrails チュートリアルをベースに実装したので詳細内容は以下のリンクを参照してください。
https://railstutorial.jp

ログイン画面にreCAPTCHAをテストするため、以下のrails チュートリアルのOpenソースを使いました。
https://github.com/yasslab/sample_apps

#作業手順
1. Google reCAPCHA管理コンソールからサイトキーとシークレットキー取得
2. ヘッダーにreCAPTCHAのスクリプト追加
3. ログインフォームにHidden追加
4. サーバーサイド実装

#Google reCAPCHA管理コンソール
## 新規登録
以下のリンクから新規登録するとサイトキーとシークレットキーが発行されますので覚えておきましょう。
https://www.google.com/recaptcha/admin/create
![スクリーンショット 2019-12-21 23.04.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/65501154-2111-0200-a8fd-dee4a4f78c21.png)

## 登録完了
登録が完了すると以下のような不正リクエストの状況を確認できる管理サイトが追加されます。
![スクリーンショット 2019-12-21 23.07.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/e8bcd918-4c6b-a377-405f-ed822fca8054.png)

#ヘッダーにreCAPTCHAのスクリプト追加
```erb:application.html.erb
<script src="https://www.google.com/recaptcha/api.js?render=reCAPCHAサイトキー"></script>
<script>
        grecaptcha.ready(function () {
            grecaptcha.execute('reCAPCHAサイトキー', { action: 'contact' }).then(function (token) {
                var recaptchaResponse = document.getElementById('recaptchaResponse');
                recaptchaResponse.value = token;
            });
        });
</script>
```

#ログインフォームにHidden追加
ログイン画面にHidden追加
`<input type="hidden" name="recaptcha_response" id="recaptchaResponse">`

```erb:sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(:session, url: login_path) do |f| %>
      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= link_to "(forgot password)", new_password_reset_path %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>Remember me on this computer</span>
      <% end %>
      <input type="hidden" name="recaptcha_response" id="recaptchaResponse">

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>
```

#サーバーサイド実装
```ruby:SessionsController
require 'uri'
require 'net/http'

class SessionsController < ApplicationController

  RECAPTCHA_SECRET_KEY = 'reCAPTCHAコンソールからシークレットキーを入力'
  RECAPTCHA_SITEVERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify'

  # POST /login
  def create
    # siteverifyAPIを呼び出してボットかチェック
    siteverify_uri = URI.parse("#{RECAPTCHA_SITEVERIFY_URL}?response=#{params[:recaptcha_response]}&secret=#{RECAPTCHA_SECRET_KEY}")
    response = Net::HTTP.get_response(siteverify_uri)
    json_response = JSON.parse(response.body)

    # reCAPTCHAのレスポンスデータが成功かスコアが0.5以上なら人間のアクションだと判断
    # スコアについては運用中変更する必要がある
    if json_response['success'] && json_response['score'] > 0.5
      user = User.find_by(email: params[:session][:email])
      if user && user.authenticate(params[:session][:password])
        if user.activated?
          # Success
          log_in user
          params[:session][:remember_me] == '1' ? remember(user) : forget(user)
          redirect_back_or user
        else
          message  = "Account not activated. "
          message += "Check your email for the activation link."
          flash[:warning] = message
          redirect_to root_url
        end
      else
        # Failure
        flash.now[:danger] = 'Invalid email/password combination'
        render 'new'
      end
    else
      flash.now[:danger] = '不正アクセスです。'
      render 'new'
    end
  end

end

```
`siteverify`APIが返すscoreが「1」に近いのがより人間のアクション、「0」に近い場合はボットの可能性が高い意味です。

これだけでGoogle reCAPTCHAが画面からのアクションを監視します。
以下の画面の右下にreCAPTCHAに保護されているのが表示されます。
![スクリーンショット 2019-12-21 23.21.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108475/96201604-132e-3438-b883-9e1c6805a307.png)

３人のアカウントを事前に登録してアカウントを変えてログインしたら最初ログインに成功したユーザー以外のアカウントではログインできませんでした。

#まとめ
こんなに簡単にボットからサイトを守れるのはすごい、問題はどれぐらい正確に判断できるのかですね。
今回は実装だけやってみましたが次回はCapybaraを利用してテストしてみます。