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分経つと認証エラーになる模様です。入力する分量が多かったり、なにか資料などを確認する必要があったりすると割と簡単に経過しそうな時間なので、特に不正ではなくとも普通にエラーが発生しそうです(しました)。また、設定時間の変更はできないようです。
対応:送信ボタンクリック時にトークンを取得する
トークン取得のタイミングを送信ボタンクリック時にすることで、タイムアウトを防ぎます。
<form id="myform">
<input type="text" name="hoge">
<input type="hidden" id="recaptchaToken">
<input type="submit" value="送信">
</form>
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 エラーが発生しても通常通りの処理をするように対応しました。
エラー内容の取得
$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
)
)
エラー時の判定
複数のエラーが発生していた場合(発生しうるのか?)はちゃんとエラー扱いにしたいので、ここではエラーコードの個数もカウントしています。
if( count($error) == 1 && $error[0] == 'timeout-or-duplicate' ){
//timeout-or-duplicateエラーを無視する処理
}else{
// 失敗処理
}
注意点
上記はPHPでの処理例ですが、レスポンスのerror-codes
がハイフンを含むため、{}
で囲う必要があります。
// エラー
$error = $verify->error-codes;
// OK
$error = $verify->{'error-codes'};
あとどうでもいいけどテストする時にいちいち5分待つのが地味にめんどくさいですね。