PHP + Ajax(jQuery) + reCAPTCHA v3 を実装する

[reCAPTCHA v3](https://developers.google.com/recaptcha) とは、クローラーやロボットによるアクセスを制限して、無駄にサイトアクセスさせないために設置する認証装置です。

主に、連投されては困るようなコンタクトフォームや、コメント投稿、ユーザー登録などの画面に設置して、そのボタンを押すのが人間なのかどうかを判定します。人間でないと判断した場合は、エラーを出すなどして実行できないようにします。

v2 までは、画像などの認証でしたが、v3からは、ページにおけるユーザーの動きを見て、その判定を自動で行うので、ユーザーにとっては何も行う必要もなく負担をかけることもありません。

## Ajax を使って reCAPTCHA 認証する

書いてみると意外と簡単ですが、これを行うには、reCAPTCHA 自体の仕様というか、全体の動きを把握してないとコードに落とし込みにくいと思います。

![reCAPTCHAイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30945/1e3c9fc7-3463-e361-3abd-ddd6d1de0371.png)


1. 認証が必要なページにGoogle指定のJavaScriptファイルを読み込む
2. サイトキーを渡して、Googleのスクリプトでtokenを生成する
3. token を submit などのタイミングでPOST送信する
4. 自分のサイト上で受け取った token をGoogleのURLへ「シークレットキー」と一緒に投げる
5. 認証可否が返ってくる（success: true, または、score: 0〜1など）
6. その認証可否を参考にしてサーバ上で処理分けする

以上が、だいたいの流れになります。

これを理解した上で、順にコードに落とし込んでいきます。

## 「サイトキー」と「シークレットキー」を取得する

reCAPTCHA を使用するには、Googleアカウントが必要です。ログインした上で、それぞれのキーを取得するには、以下にアクセスします。

<https://g.co/recaptcha/v3>

![key.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30945/bcaa0668-d363-d5b7-4a0e-4cc3c1be47b0.png)

これらのキーはいわゆる公開鍵のようなイメージでしょうか。その名のとおり「サイトキー」は、HTML 上で確認できるように記述します（ユーザーに見える必要はないですが、覗こうと思えば覗けるという意味です）。その一方で「シークレットキー」は、秘密鍵として自身のサーバ上に記述します。

## 認証が必要なページに Google 指定の JavaScript ファイルを読み込む

公式ドキュメント
<https://developers.google.com/recaptcha/docs/v3>

にもありますが、必要なページの「&lt;/body&gt;」タグの手前付近で、Google指定のJavaScriptファイルを記述します。

```html

<script src="https://www.google.com/recaptcha/api.js?render=サイトキー"></script>
```

## サイトキーを渡して、Google のスクリプトで token を生成する

次に、あらかじめGoogleスクリプトによって、token を生成してどこかに保持しておく必要があります。

もし `<form>`タグを作っているようなら、

```html
<input type="hidden" name="recaptcha_response" id="recaptchaResponse">
```

と、hidden 属性の input タグに埋め込んでしまっても良いでしょう。HTML ファイルが読み込まれると同時に、token を生成して、このタグに格納します。

```javascript
$(document).ready(function() {
  grecaptcha.ready(function () {
    grecaptcha.execute('サイトキー', { action: 'contact' }).then(function (token) {
        var recaptchaResponse = $('#recaptchaResponse');
        recaptchaResponse.value = token;
    });
  });
});
```

ただ、今回は、Ajax を使って POST するので、グローバル変数に格納することで留めておきましょう。

```javascript
// グローバル変数
var tokenData;

$(document).ready(function() {
  // Google reCAPTCHA v3 のトークンを生成
  grecaptcha.ready(function () {
    grecaptcha.execute('サイトキー', { action: 'submit' }).then(function (token) {
      tokenData = token;
    });
  });
});
```

なお、`action` に入力される文字列ですが、ここは書き分けることで、[admin console](https://g.co/recaptcha/admin/)から「リクエスト数」として集計・参照することができます。ですので、あまり集計や、後の参照を気にする必要がない場合は、文字列はなんであっても良いということです。

## token を submit などのタイミングで POST 送信する

そして、上記で保存したグローバル変数を Ajax の POST 値に載せて送信します。

```javascript
function sendData() {
  $.ajax({
    type: "POST",
    url: "register_submit.php",
    timeout: 10000,
    cache: false,
    data: {
      'mail': "m@hibara.org",
      'password': "password123",
      'recaptchaResponse': tokenData
    },
    dataType: 'text'
  })
  .done(function (data) {
    // "OK"
    location.href = "register_complete.php";
    return true;
  })
  .fail(function () {
    alert("サーバー内でエラーがあったか、サーバーから応答がありませんでした。");
    return false;
  })
  .always(function (data_or_jqXHR, textStatus, jqXHR_or_errorThrown) {
  });
}
```

## 自分のサイト上で受け取った token を Google の URL へ「シークレットキー」と一緒に投げる

投げた先、今回上記の例で言うと `register_submit.php` の中身はこう書きます。

```php
if(isset($_POST['recaptchaResponse']) && !empty($_POST['recaptchaResponse'])) {
  $secret = 'シークレットキー';
  //get verify response data
  $verifyResponse = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='.$secret.'&response='.$_POST['recaptchaResponse']);
  $responseData = json_decode($verifyResponse);

  if ( $responseData->success ) {
    if ( $responseData->score < 0.8 ) { // おまえ人間じゃねえ！
      // 認証スコアが低い
      exit();
    }
  }
  else {
    // 認証失敗
    exit();
  }
}
else {
  // POST値が正常に投げられてこなかった
  exit();
}

// ここまで抜けてきたとき、正常処理を行う

```

ここのスコア `$responseData->score` は、0〜1 の間を取ります。1に近いほど人間と判断できます。私が何回か試行してみた実感としては、ほぼ `0.9`以上のスコアを叩くような気がします。この値については、サイトによって試行錯誤する必要があるかもしれません。

ここで得られる他の値、結果については、先に挙げた[公式ドキュメント](https://developers.google.com/recaptcha/docs/v3)にあるとおりです。

```JSON
{
  "success": true|false,      // whether this request was a valid reCAPTCHA token for your site
  "score": number             // the score for this request (0.0 - 1.0)
  "action": string            // the action name for this request (important to verify)
  "challenge_ts": timestamp,  // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
  "hostname": string,         // the hostname of the site where the reCAPTCHA was solved
  "error-codes": [...]        // optional
}
```

JSONでの例になっていますが、この仕様に沿って、上記のPHPファイル内で処理すれば問題なく取得できるでしょう。

## まとめ

とりあえず、Ajaxで reCAPTCHA v3 を実装した例が少なく、仕様全体のイメージもしにくかったので記事にしてみました。「最初にトークンを生成して投げる」ということを知れば、あとはいくらでも応用が利くとは思います。