reCAPTCHA v3の認証エラー(timeout-or-duplicate)対応

reCAPTCHA v3 で、エラーになってほしくない場面で発生していたエラー( timeout-or-duplicate )に対応したメモです。

    reCAPTCHA v3 とは

    reCAPTCHAはbotを判別してフォームをスパムから守る仕組みです。

    最新バージョンの reCAPTCHA v3 ではユーザーの行動から算出したスコアでbotを識別しており、いままでのように「私はロボットではありません」にチェックを入れたり、読みにくい文字を読んだり画像をしたり…というユーザーの手間がなくなりました。

    エラーの種類

    Error code Description
    missing-input-secret シークレットパラメータなし
    invalid-input-secret シークレットパラメータが無効または不正な形式
    missing-input-response 応答パラメータなし
    invalid-input-response 応答パラメータが無効または不正な形式
    bad-request リクエストが無効または不正な形式
    timeout-or-duplicate 応答は無効になりました : 古すぎるか、以前に使用されたことがある

    timeout-or-duplicate エラー

    いくつかあるエラーのうち、特にクセモノなのがtimeout-or-duplicate(タイムアウトまたは既に使われた値)のエラーです。

    特にタイムアウトは2分に設定されているらしく、フォーム画面を開いてトークンを取得してからページを開いたまま2分経つと認証エラーになる模様です。入力する分量が多かったり、なにか資料などを確認する必要があったりすると割と簡単に経過しそうな時間なので、特に不正ではなくとも普通にエラーが発生しそうです(しました)。また、設定時間の変更はできないようです。

    対応:送信ボタンクリック時にトークンを取得する

    トークン取得のタイミングを送信ボタンクリック時にすることで、タイムアウトを防ぎます。

    HTML
    <form id="myform">
    <input type="text" name="hoge">
    <input type="hidden" id="recaptchaToken">
    <input type="submit" value="送信">
    </form>
    JavaScript
    const myform = document.querySelector('#myform');
    const submit = document.querySelector('[type="submit"]');
    const set_token = function(event){
      event.preventDefault();
      grecaptcha.ready(function() {
        grecaptcha.execute('recaptcha_site_key', {action: 'submit'}).then(function(token) {
          const recaptchaToken = document.querySelector('#recaptchaToken');
          recaptchaToken.value = token;
          myform.submit();
        });
      });
    }
    submit.addEventListener('click', set_token);

    追記:以下の対応は真似しない方がよい

    本記事公開当時、次項からの内容を掲載していたが、不適切とのご指摘をいただいたので上記内容に書き直した。

    エラーを無視するのではなく他の方法で回避すべきで、次項以下に記載の対応は真似しない方がよい。

    しかしこのエラーコードは名前の通り、トークン重複利用の場合でも返ってくる。要は一回人間が操作したときのreCAPTCHAのフォーム値が再利用できるようになるということで、いくらでもbotが通過できるようになり、reCAPTCHAが無意味になる。

    (略)

    このエラーの発生を防ぐためには、reCAPTCHA v3についてはユーザアクション時(例えばフォーム送信時)にトークン発行することをドキュメントでは勧めている。

    reCAPTCHAのtimeout-or-duplicateは無視してはいけない – 仮メモ

    Note: reCAPTCHA tokens expire after two minutes. If you’re protecting an action with reCAPTCHA, make sure to call execute when the user takes the action rather than on page load.

    reCAPTCHA v3  |  Google Developers

    以下、反省の為に公開時のものをそのまま残している。非推奨の実装だが、デバッグの参考くらいにはなるかもしれない。

    【非推奨】timeout-or-duplicate エラーを無視する

    今回は timeout-or-duplicate エラーが発生しても通常通りの処理をするように対応しました。

    エラー内容の取得

    PHP
    $response = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' . $recaptcha_secret_key . '&response=' . $recaptcha_token);
    $verify = json_decode($response);
    
    if($verify->success) {
    	// 成功
    } else {
    	// 失敗
    	$error = $verify->{'error-codes'};
    }

    レスポンスの中身

    $verifyをログに出すと以下のようなレスポンスが返ってきていました。success の値は bool が入る。

    成功時

    stdClass Object
    (
    	[success] => 1
    	[challenge_ts] => 2020-07-13T03:36:27Z
    	[hostname] => memdx.com
    	[score] => 0.9
    	[action] => submit
    )

    エラー時

    stdClass Object
    (
    	[success] => 
    	[error-codes] => Array
    		(
    			[0] => timeout-or-duplicate
    		)
    )

    エラー時の判定

    複数のエラーが発生していた場合(発生しうるのか?)はちゃんとエラー扱いにしたいので、ここではエラーコードの個数もカウントしています。

    PHP
    if( count($error) == 1 && $error[0] == 'timeout-or-duplicate' ){
    	//timeout-or-duplicateエラーを無視する処理
    }else{
    	// 失敗処理
    }

    注意点

    上記はPHPでの処理例ですが、レスポンスのerror-codesがハイフンを含むため、{}で囲う必要があります。

    PHP
    // エラー
    $error = $verify->error-codes;
    
    // OK
    $error = $verify->{'error-codes'};

    あとどうでもいいけどテストする時にいちいち5分待つのが地味にめんどくさいですね。

    参考

    サポートが必要ですか?

    ご質問・お見積り依頼はお気軽にどうぞ

    お問い合わせはこちら
    シェア
    野良人 代表
    新免祥太
    1988年岡山生まれ。外食企業のWEB・EC担当を経験したのち、2013年12月より「野良人(のらんど)」の屋号で独立しWEBデザイン・プログラミングなどWEBサイト制作の工程全般を請け負っています。お気軽にご相談ください。
    広告
    次の記事(2020/12/31)
    【Stripe】ダッシュボードで作成した商品のCheckout決済を実装する
    前の記事(2020/12/05)
    Local(local by flywheel)がDockerのTLS証明書エラーで起動できずにループする現象に対応
    記事一覧