reCAPTCHA v3 を試す

今更ではありますが、 reCAPTCHA v3 の設置を試したのでメモしておきます。

設定

reCAPTCHAのページにアクセスし、設定を進めていきます。

reCAPTCHA v3 の設定・コンソールへログイン
reCAPTCHA v3 の設定・コンソールへログイン

まずは「v3 Admin Console」をクリックしてログイン。

reCAPTCHA v3 の設定・新規追加
reCAPTCHA v3 の設定・新規追加

新規でプロパティを追加するので右上の「+」をクリック。

reCAPTCHA v3 の設定・必要事項の記入や選択
reCAPTCHA v3 の設定・必要事項の記入や選択

必要事項を記入、 reCAPTCHA v3 を選択し、設置するドメインを記述。

reCAPTCHA v3 の設定・サイトキーとシークレットキーを控える
reCAPTCHA v3 の設定・サイトキーとシークレットキーを控える

発行後、 reCAPTCHA のキー から「サイトキー」と「シークレットキー」を控えます。なお、シークレットキーは自分以外には見えないようにご注意を。

PHP プログラムにも記述しますが、それはフロントには見えないようにします。

これで準備は完了。

コード

次にコードを。

フォーム (HTML)

<form method="post" action="./procedure.php">
    <input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response"><!-- 追加 -->
    <button type="submit" class="btn btn-primary">送信する</button>
</form>

まずは HTML 。必要なのは reCAPTCHA のトークンを value に含むための hiddeninput 要素。

その他の部分は通常のフォームと同様に作っていきます。

<script src="https://www.google.com/recaptcha/api.js?render=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" defer></script><!-- 控えたサイトキーを render の後に記述 -->
<script src="./js/app.js" defer></script>

また、フォームの HTML で API と JavaScript の読み込みをします。

JavaScript (app.js)

window.addEventListener('load', () => {
    const siteKey = `XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`; // 控えたサイトキーを記述
    grecaptcha.ready(function() {
        try {
            // 全体を try catch で括ってエラーの場合にも備える
            grecaptcha.execute(
                siteKey,
                {
                    action: 'homepage'
                }
                ).then(function( token ) {
                // API から返却されたトークンをセット
                const recaptchaResponseDom = document.querySelector('#g-recaptcha-response');
                recaptchaResponseDom.value = token;
            });
        } catch (e) {
            console.error(e.message);
        }
    });
});

読み込む JavaScript は reCAPTCHa のスクリプトと API 読み込みを前提として専用のコードを記述していきます。

サンプルでは submit ボタンクリック時にブラウザのデフォルトの submit の動作をキャンセル (e.preventDefault()) して JS で submit するものが散見されたのですが、個人的にその挙動はさせたくなかったので API を叩いてトークンをセットする部分のみを記述しました。

PHP (procedure.php)

最後に submit 後の処理部分を。

<?php

$siteKeyID = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
$secretKey = 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY';

function err400() {
    header('HTTP/1.1 400 Bad Request.');
    include(__DIR__ . '/error.html');
    exit;
}
function err500() {
    header('HTTP/1.1 500 Internal Server Error.');
    include(__DIR__ . '/error.html');
    exit;
}
function success() {
    header('HTTP/1.1 200 OK.');
    include(__DIR__ . '/finish.html');
    exit;
}

// filter_input で意図した値が拾えない場合は 400 を返却する
$captchaResponse = filter_input(INPUT_POST, 'g-recaptcha-response');
if(!isset($captchaResponse) || empty($captchaResponse) || !is_string($captchaResponse)) {
    error_log('reCAPTCHA のレスポンスコードがセットされていません。');
    err400();
}

// APIリクエスト
$verifyResponse = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' . $secretKey . '&response=' . $captchaResponse);

// APIレスポンス確認
$responseData = json_decode($verifyResponse);
// 認証に失敗した場合は 500 を返却する

// パターン1 デフォルト
if ($responseData->success) {
    success();
} else {
    error_log('error: reCAPTCHA の認証でエラーが発生しました。');
    err500();
}
// パターン2 スコアも判定に使用し、あえて人間でも引っかかるように閾値を高くする。 0.9 前後が平均値の模様なので 0.95 を閾値にすると大体引っかかるようになる
if ($responseData->success && $responseData->score >= 0.95) {
    error_log('info: reCAPTCHA の認証が成功しました。');
    error_log('score:' . $responseData->score);
    success();
} else {
    error_log('error: reCAPTCHA の認証でエラーが発生しました。');
    error_log('score:' . $responseData->score);
    err500();
}

やっていることとしては以下。

  • g-recaptcha-response (HTML 側で記述した name属性) の POSTパラメータ を読み取り、それと冒頭で控えたシークレットキーで API へ突き合わせを実行
  • 結果は file_get_contents() 等で取得、 JSON 形式なので json_decode() でデコード
  • デコードしたオブジェクトの success, score プロパティ を使用して判定の結果によりエラーか処理続行かを分岐

これで何度か手動で試験して、意図した挙動になったことを確認しました(パターン1はOK、パターン2はNG)。

参考

reCAPTCHA v3

Invisible reCAPTCHA

JavaScript

複数回マッチの正規表現

配列

Webpack で外部変数を読み込み

PHP

filter_input

エラーページ

header

ログ出力

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。