Google reCAPTCHA v3はどうするとスパム判定されるのか?

思うところがあって[reCAPTCHA v3](https://developers.google.com/recaptcha/)を触ってみたく、試してみました。

v2も合わせて読みたい。
https://qiita.com/kaibadash@github/items/2681f02ef16ad6421da1

# Google reCAPTCHA v3

いわゆる `「ロボットではありません」` は誰もが触ったことがあると思います。
それが表示されるのは Google reCAPTCHA v2 で、v3はそれが表示されないのですが、v3が新しくてよい、というわけではなく以下のような区別のようです。

- v2: スパムの判定をGoogleに任せる
- v3: スパムの判定を自分で行い、細かい制御を行う
    - ハンドリングしてあとから制御したりできるメリットが有る
    - ロボットじゃないかもしれないしね

# 今回、ターゲットになるフォーム

僕の実験サイトなので好きに触ってみてください。

https://pokosho.com/a/recaptcha/

よくあるお問い合わせフォームになります。
form名もcontactで悪いbotさんに拾ってもらえそう？

# key, secretの発行

https://www.google.com/recaptcha/admin/create
ここからkeyとsecretを発行します。

keyはフロントエンドに埋め込むので誰に見られても良いものですがsecretはその名の通り秘密にすべきもので、バックエンドで使います。

作成済みプロジェクトは以下から、「v3 Admin Console」からkey, secretを見られる。
一覧で見せて欲しいですね。

https://www.google.com/recaptcha/about/

# フロントエンド

Googleにkeyを渡し、tokenを受け取り、バックエンドにわたす必要があります。

```html
<input type="hidden" name="recaptcha" id="recaptcha" />
```

hiddenパラメータを用意しました。

```javascript
<script src="https://www.google.com/recaptcha/api.js?render=[key]"></script>
```

recaptchaのスクリプトにkeyを渡して読み込みます。
`[key]` には取得したkeyを入れてくださいね。

```javascript
grecaptcha.ready(function() {
  grecaptcha.execute('[key]', {action: 'homepage'}).then(function(token) {
    var recaptcha = document.getElementById('recaptcha');
    recaptcha.value = token;
  });
});
```

`[key]` には取得したkeyを入れてくださいね。
`grecaptcha.execute`を呼ぶとtokenが得られますので、上記のhiddenパラメータに入力しておきます。
actionはformのactionではなく、Googleの指定するactionで、 `homepage`, `login `, `social`, `e-commerce` のいずれかを指定します。
名前からある程度わかりますが、詳しくはドキュメントを参照ください。

# バックエンド

verify の結果を表示しします。

```php
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>reCAPTCHA</title>
</head>
<body>

<?php
if (isset($_POST["recaptcha"]) && !empty($_POST["recaptcha"])) {
    $secret = "SECRET"; // TODO:要変更
    $verifyResponse = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=".$secret."&response=".$_POST["recaptcha"]);
    echo $verifyResponse;
} else {
    echo "invalid request: recaptcha is not found.";
}
?>
</html>
```

# 試してみた

## 正常系
```json
{
  "success": true,
  "challenge_ts": "2019-08-16T14:21:18Z",
  "hostname": "pokosho.com",
  "score": 0.9,
  "action": "homepage"
}
```

高いスコアです。

## JSでクリックしてみる

Chrome Developer Toolを立ち上げ、consoleからJSでクリックしてみる。

```js
document.getElementById("submit").click();
```

```json
{
  "success": true,
  "challenge_ts": "2019-08-16T14:21:40Z",
  "hostname": "pokosho.com",
  "score": 0.9,
  "action": "homepage"
}
```

なんだと？
v2の「私はロボットではありません」に対してこれをやると怒られたのですが…!
ブックマークレットとかも使えそうですね。

## 同じリクエストを飛ばしてみる

Chrome Developer ToolからCopy as cURLする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21799/b707b0b1-c6ae-0052-a893-cc8b759e93ef.png)

```json
{
  "success": false,
  "error-codes": [
    "timeout-or-duplicate"
  ]
}
```
おお〜。

## headless chrome(puppeteer)

puppeteer で操作してみる。
乱暴にいきなりsubmitを押す、という乱暴な操作です。

https://github.com/kaibadash/puppeteer_recaptcha/blob/main/index.js

```js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://pokosho.com/a/recaptcha/',
    {'timeout': 10000, 'waitUntil':'load'}
  );
  await page.waitForTimeout(1000); 
  await page.click('#submit');
  const json = await page.content();
  console.log(json);
  await browser.close();
})();
```

```
$ node index.js
<!DOCTYPE html><html><head>
  <meta charset="utf-8">
  <title>reCAPTCHA</title>
</head>
<body>

{
  "success": true,
  "challenge_ts": "2022-02-20T12:35:21Z",
  "hostname": "pokosho.com",
  "score": 0.9,
  "action": "homepage"
}

</body></html>
```

あれ…？ puppeteerは人間だった…？
ガードしたい対象が違うのか？

# まとめ

- 「私はロボットではありません」の印象が強すぎて「UXを損ねるのでは？」という懸念が挙がるがそんなことはない
- JS読み込んで終わり、というわけにはいかない。botかどうかの判定はバックエンドで行う。
- Googleからドメインを指定してkeyとsecretを発行する。
- keyはフロントエンドからGoogleに渡す。するとtokenが得られる。
- tokenをバックエンドに渡す。
- バックエンドはtokenとsecretをGoogleに渡す。すると判定結果が得られる。
- 判定結果はbool値の `success` のパラメータ見れば良い。
- ReactやVueでやる場合、[ライブラリ](https://www.npmjs.com/package/react-recaptcha-v3)を使うのが良さそうですね。
- 登場人物は多い。駆け出しエンジニアには難しいかも知れない。