WordPress に reCAPTCHA v3 をプラグインを使わず実装
はじめに
プラグインは使わず、WordPress に reCAPTCHA v3 を実装するカスタマイズの紹介です。以下の 3 つのフォームに実装します。
- コメントフォーム
- ログインフォーム
- ログインフォームの「パスワードをお忘れですか ?」から移動するパスワードのリセット手順のリクエストフォーム ※ これ以降はパスワードリセットフォームと呼びます
カスタマイズは、「サイトへの設置」と「サーバーサイドでの確認」の 2 ステップで終わります。
尚、以下の環境で動作を確認しました。
- WordPress 6.0.1
- 公式テーマ Twenty Twenty-Two
- 公式テーマ Twenty Twenty-One
- 公式テーマ Twenty Twenty
- 人気テーマ Cocoon
もし、reCAPTCHA v2 を WordPress に実装する場合は、WordPress に reCAPTCHA v2 をプラグインを使わず導入する方法 をご覧ください。
サイトへの設置
サイトへの設置では、まず <form>
に <input>
を追加します。追加する場所は、各フォームの送信ボタンの上部です。
// 追加する HTML
function bit_add_html() {
return '<input type="hidden" id="grecaptcha-response" name="grecaptcha-response">';
}
// コメントフォームに HTML を追加
add_filter( 'comment_form_submit_field', function( $submit_field ) {
return bit_add_html() . $submit_field;
});
// ログインフォームに HTML を追加
add_action( 'login_form', function() {
echo bit_add_html();
});
// パスワードリセットフォームに HTML を追加
add_action( 'lostpassword_form', function() {
echo bit_add_html();
});
もし、reCAPTCHA のバッジを CSS で非表示にする場合は、代わりとなるテキスト を bit_add_html()
に追加します。
function bit_add_html() {
$html = '<input type="hidden" id="grecaptcha-response" name="grecaptcha-response">';
$html .= '<p>This site is protected by reCAPTCHA and the Google
<a href="https://policies.google.com/privacy">Privacy Policy</a> and
<a href="https://policies.google.com/terms">Terms of Service</a> apply.</p>';
return $html;
}
次は、reCAPTCHA のトークンを発行する JavaScript の追加です。
// reCAPTCHA のサイトキーとシークレットキーを入力
function bit_recaptcha_key() {
return [
'site_key' => 'ここにサイトキーを入力',
'secret_key' => 'ここにシークレットキーを入力'
];
}
// 追加する JavaScript
function bit_add_js() {
$site_key = bit_recaptcha_key()['site_key'];
$src = 'https://www.google.com/recaptcha/api.js?render=' . $site_key;
wp_enqueue_script( 'bit-recaptcha', $src, [], false, true );
// $target_form は各フォームの id にする
if ( is_singular() ) {
$target_form = 'commentform';
$action = 'homepage';
} elseif ( $GLOBALS['pagenow'] === 'wp-login.php' && ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] === 'lostpassword' ) ) {
$target_form = 'lostpasswordform';
$action = 'login';
} else {
$target_form = 'loginform';
$action = 'login';
}
$js = '
const target = document.getElementById("' . $target_form . '");
if (target !== null) {
const response = document.getElementById("grecaptcha-response");
const submitting = () =>
HTMLFormElement.prototype.submit.call(target);
const setToken = () => {
try {
grecaptcha.ready(() => {
grecaptcha.execute(
"' . $site_key . '",
{ action: "' . $action . '" }
)
.then(token => response.value = token);
});
} catch {
submitting();
}
};
const observer = new MutationObserver(() => submitting());
target.addEventListener("submit", event => {
event.preventDefault();
setToken();
observer.observe(response, { attributeFilter: ["value"] });
});
}
';
wp_add_inline_script( 'bit-recaptcha', $js );
}
// コメントフォームのあるページに JavaScript を追加
add_action( 'wp_enqueue_scripts', function() {
if ( is_singular() && comments_open() ) {
bit_add_js();
}
});
// ログインページとパスワードリセットフォームのあるページに JavaScript を追加
add_action( 'login_enqueue_scripts', 'bit_add_js' );
// JavaScript の読み込みの最適化
add_filter( 'script_loader_tag', function( $tag, $handle ) {
if ( $handle === 'bit-recaptcha' ) {
$js_id = 'id=\'bit-recaptcha-js\'';
$js_after_id = 'id=\'bit-recaptcha-js-after\'';
$tag = str_replace( $js_id, $js_id . ' defer', $tag );
$tag = str_replace( $js_after_id , $js_after_id . ' type="module"', $tag );
if ( $GLOBALS['pagenow'] === 'wp-login.php' ) {
$tag = str_replace( 'type=\'text/javascript\' ' , '', $tag );
}
}
return $tag;
}, 10, 2 );
bit_recaptcha_key()
に reCAPTCHA のサイトキーとシークレットキーを入力します。
気を付けたいのが、reCAPTCHA のトークンの有効期限です。reCAPTCHA のトークンの有効期限は、発行してから 2 分間です。
もし、サーバーサイドでの確認を 2 分を超えてから行った場合は、以下のエラーコードが reCAPTCHA API から返ってきます。
{
"success": false,
"error-codes": ["timeout-or-duplicate"]
}
そのため、公式ドキュメント には以下の注意書きがあります。
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.
Google 翻訳:reCAPTCHA トークンは 2 分後に期限切れになります。 reCAPTCHA でアクションを保護している場合は、ページの読み込み時ではなく、ユーザーがアクションを実行したときに必ず execute を呼び出してください。
公式ドキュメントに倣い、トークンを発行するタイミングはフォームの送信時にしています。フォームの送信時には、このような処理を行っています。
Event.preventDefault()
でフォームの送信を停止します。- reCAPTCHA のトークンを発行します。
- reCAPTCHA のトークンが
<input id="grecaptcha-response">
のvalue
に追加されたらフォームを送信します。 - 何かしらの理由で reCAPTCHA API に接続できない場合は、フォームを送信します。サーバーサイドでの確認でエラーの判定となりますが、いつまで経っても送信しないより良いと考えました。
これでサイトへの設置は終わりです。
サーバーサイドでの確認
grecaptcha-response
の名前で reCAPTCHA のトークンがフォームと一緒に送信されます。このトークンがない時、あるいは API へのリクエストでエラーや低い数値のスコアが返ってきた時に、wp_die()
でエラー画面を表示します。
// reCAPTCHA の確認処理
function bit_verify_recaptcha() {
$verify_error = [
'text' => '認証エラーが発生しました。時間を置いてお試しください。',
'wp_die_args' => ['response' => 401, 'back_link' => true]
];
$bot_error = [
'text' => '不正なプログラムと判定されたため、処理を停止しました。',
'wp_die_args' => ['response' => 403]
];
if ( isset( $_POST['grecaptcha-response'] ) ) {
$url = 'https://www.google.com/recaptcha/api/siteverify?secret=';
$url .= bit_recaptcha_key()['secret_key'];
$url .= '&response=';
$url .= $_POST['grecaptcha-response'];
$recaptcha = wp_remote_request( $url );
$recaptcha = wp_remote_retrieve_body( $recaptcha );
$recaptcha = json_decode( $recaptcha );
if ( ! isset( $recaptcha->success ) || ! $recaptcha->success ) {
wp_die( $verify_error['text'], '', $verify_error['wp_die_args'] );
}
if ( ! isset( $recaptcha->score ) ) {
wp_die( $verify_error['text'], '', $verify_error['wp_die_args'] );
} elseif ( $recaptcha->score <= 0.5 ) {
wp_die( $bot_error['text'], '', $bot_error['wp_die_args'] );
}
} else {
wp_die( $verify_error['text'], '', $verify_error['wp_die_args'] );
}
}
// コメントフォーム送信時に確認処理を行う
add_action( 'pre_comment_on_post', 'bit_verify_recaptcha' );
// ユーザーのログイン時に確認処理を行う
add_action( 'wp_login', 'bit_verify_recaptcha' );
// パスワードリセットフォームの送信時に確認処理を行う
add_action( 'lostpassword_post', 'bit_verify_recaptcha' );
これで reCAPTCHA v3 の実装は終わりです。
無料版と有料版の違い
最後に、reCAPTCHA の無料版と有料版の参考情報を記載します。
reCAPTCHA には、有料版の reCAPTCHA Enterprise があります。無料版と有料版の違いは、reCAPTCHA のバージョン間の機能の比較 をご参考ください。
上記ページに記載の「スコアの粒度」については、スコアの解釈 をご参考ください。
尚、今回の方法では、スコアが 0.5 以下をボットとして扱っています。