2026年7月26日

2026年7月26日

WordPressにリスクベース認証(Risk-Based Authentication)を導入する方法

リスクベース認証(Risk-Based Authentication)は、ログインのたびに「いつもの端末か」「直前に失敗が続いていないか」「管理者アカウントか」といった状況を評価し、危険度が高いときだけ追加の確認や通知を行う考え方です。WordPress本体にこの機能はないため、2段階認証プラグインの「信頼済み端末」系の機能を使う、外部の認証基盤(IdP)の条件付きアクセスに任せる、小さなコードで検知と通知を自作する、のいずれかで実現します。この記事では、評価に使うシグナルの選び方、それぞれの方法の向き不向き、新しい端末からのログインを検知して通知するコード例、運用時の注意点を解説します。

リスクベース認証の考え方

パスワードだけのログインは、パスワードが漏れた時点で突破されます。かといって毎回2段階認証を求めると、利用者の負担が大きくなり、特に会員サイトでは離脱の原因になります。リスクベース認証は、その中間を取る方法です。

判断の流れは次の3段階に分けて考えると整理しやすくなります。

  1. シグナルを集める: 端末、IPアドレス、時間帯、失敗回数、アカウントの権限など
  2. 危険度を評価する: シグナルを点数化し、しきい値を超えたかを判定する
  3. 対応を決める: 通常どおり通す、管理者や本人に通知する、追加の認証を求める、拒否する

いきなり「拒否」まで自動化すると、正規の利用者を締め出す事故が起きやすくなります。最初は「通知」から始め、ログを見ながら追加認証や拒否に進むのが安全な導入順です。

評価に使うシグナル

WordPressで扱いやすく、誤判定が比較的少ないシグナルは次のとおりです。

  • 端末が既知かどうか: 一度ログインした端末に識別用のCookieを保存しておき、それがない端末からのログインを「新しい端末」とみなします。最も実用的なシグナルです
  • 直前の失敗回数: 同じアカウントで短時間に何度も失敗した直後の成功は、総当たりが当たった可能性があります
  • アカウントの権限: 管理者は乗っ取られたときの被害が大きいため、同じ状況でも厳しく扱います
  • IPアドレスや国: 普段と違う国からのアクセスは有力なシグナルですが、モバイル回線やVPNで日常的に変わるため、単独で拒否の根拠にするのは避けます

IPアドレスを使う場合、CDNやリバースプロキシの配下ではWordPressから見えるIPがプロキシのものになります。サーバー側で実際の送信元IPを渡す設定をしてから使ってください。

実現方法の選び方

2段階認証プラグインの機能を使う

もっとも手軽なのは、2段階認証プラグインの設定でリスクに応じた運用に近づける方法です。プラグインによって、権限グループごとに2段階認証を必須にする機能や、一度確認した端末では一定期間コードの入力を省略する機能があります。たとえばSolid Security(旧iThemes Security)の有料版には、信頼済みの端末かどうかで扱いを変える機能があります(2026年10月現在)。機能の名称や提供範囲は今後変わる場合があるため、導入前に公式の説明で確認してください。

「管理者だけは2段階認証を必須、一般会員は任意」という設定だけでも、権限というシグナルを使ったリスクベースの運用になります。

外部の認証基盤(IdP)に任せる

社内向けサイトや、従業員が管理画面を使うサイトでは、Microsoft Entra ID、Okta、Auth0などの認証基盤とシングルサインオンで連携し、その条件付きアクセスやアダプティブMFAの機能にリスク判定を任せる方法があります。端末の状態や場所、サインインの危険度に応じて追加認証を求める仕組みが用意されており、自前で判定を作るより精度と管理のしやすさで勝ります。ただし、こうした機能は上位のプランでのみ提供されることが多く、費用と連携用プラグインの保守が必要になります。

管理画面だけを守ればよい場合は、Cloudflare Accessのようなゼロトラスト型のサービスで /wp-admin/ と wp-login.php の手前に認証を置く方法もあります。この場合、フロント側の機能から呼ばれる /wp-admin/admin-ajax.php は保護の対象から外す必要があります。

検知と通知を自作する

プラグインや外部サービスを増やしたくない場合は、WordPressのフックで「新しい端末からのログイン」と「直前の失敗回数」を評価し、危険度が高いときにメールで通知する仕組みを作れます。追加認証そのものは2段階認証プラグインに任せ、自作部分は検知と通知に絞ると、保守の負担を抑えられます。

新しい端末からのログインを検知して通知するコード

次のコードは、wp-content/mu-plugins/ に置くことを想定した例です。テーマの functions.php に書くとテーマ変更で無効になるため、セキュリティ関係の処理はmu-pluginにしておくと安心です。追加前にサイトのバックアップを取り、ステージング環境で動作を確かめてから本番に反映してください。

<?php
/**
 * Plugin Name: Login Risk Notice (site-specific)
 * Description: 新しい端末からのログインや失敗直後のログインを検知して通知する。PHP 7.3以降。
 */

// ログイン失敗をアカウントごとに数える(最後の失敗から15分間保持)
add_action( 'wp_login_failed', function ( $username ) {
	$user = is_email( $username ) ? get_user_by( 'email', $username ) : get_user_by( 'login', $username );
	if ( ! $user ) {
		return;
	}
	$key   = 'mysite_login_fail_' . $user->ID;
	$count = (int) get_transient( $key );
	set_transient( $key, $count + 1, 15 * MINUTE_IN_SECONDS );
} );

// ログイン成功時に危険度を評価する
add_action( 'wp_login', function ( $user_login, $user ) {
	$cookie_name = 'mysite_device';
	$known       = get_user_meta( $user->ID, 'mysite_known_devices', true );
	$known       = is_array( $known ) ? $known : array();

	$token    = isset( $_COOKIE[ $cookie_name ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ $cookie_name ] ) ) : '';
	$is_known = ( '' !== $token && in_array( hash( 'sha256', $token ), $known, true ) );

	$fail_key = 'mysite_login_fail_' . $user->ID;
	$fails    = (int) get_transient( $fail_key );
	delete_transient( $fail_key );

	$score   = 0;
	$reasons = array();
	if ( ! $is_known ) {
		$score    += 2;
		$reasons[] = '初めて使われた端末またはブラウザ';
	}
	if ( $fails >= 5 ) {
		$score    += 2;
		$reasons[] = sprintf( '直前に%d回のログイン失敗', $fails );
	}

	// 管理者はしきい値を低くして厳しく扱う
	$threshold = user_can( $user, 'manage_options' ) ? 2 : 4;

	if ( $score >= $threshold ) {
		$ip      = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '不明';
		$agent   = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '不明';
		$site    = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
		$message = sprintf(
			"%s へのログインがありました。\n\n理由: %s\n日時: %s\nIPアドレス: %s\nブラウザ: %s\n\n心当たりがない場合は、すぐにパスワードを変更してください。",
			$site,
			implode( '、', $reasons ),
			wp_date( 'Y-m-d H:i:s' ),
			$ip,
			$agent
		);
		wp_mail( $user->user_email, sprintf( '[%s] 注意が必要なログインを検知しました', $site ), $message );
	}

	// この端末を既知として登録する(1ユーザーにつき最新10台まで)
	if ( ! $is_known ) {
		$new_token = wp_generate_password( 32, false );
		$known[]   = hash( 'sha256', $new_token );
		update_user_meta( $user->ID, 'mysite_known_devices', array_slice( $known, -10 ) );

		setcookie( $cookie_name, $new_token, array(
			'expires'  => time() + YEAR_IN_SECONDS,
			'path'     => SITECOOKIEPATH,
			'domain'   => COOKIE_DOMAIN ? COOKIE_DOMAIN : '',
			'secure'   => is_ssl(),
			'httponly' => true,
			'samesite' => 'Lax',
		) );
	}
}, 10, 2 );

コードのポイントは次のとおりです。

  • 端末の識別にはランダムな値を使う: User-AgentやIPを組み合わせた「指紋」は、ブラウザの更新や回線の変化で頻繁に変わります。ランダムな値をCookieに保存し、データベースにはそのハッシュだけを残すことで、データベースが漏れてもCookieを偽造されにくくしています
  • しきい値で対応を変える: 管理者は新しい端末というだけで通知し、一般ユーザーは新しい端末と失敗の連続が重なったときだけ通知します。通知が多すぎると読まれなくなるため、運用しながら調整します
  • 拒否はしない: この例はログイン自体は通し、本人に知らせるだけです。判定の誤りで正規の利用者が締め出されることはありません

動作を確認する

まず、普段使っているブラウザでログインし直し、管理者であれば通知メールが届くことを確認します。もう一度ログインして通知が来なければ、端末の登録が機能しています。続いてシークレットウィンドウや別のブラウザでログインし、再び通知が届くかを確かめます。

通知メールが届かない場合は、サーバーからのメール送信自体が迷惑メール扱いになっている可能性があります。SMTP経由で送信するプラグインを使い、送信ドメインの認証(SPFやDKIM)を整えてから運用してください。

運用上の注意

  • プライバシーへの配慮: IPアドレスや利用端末の情報を扱うため、プライバシーポリシーにログイン時の情報の利用目的を記載します
  • Cookieの削除で「新しい端末」扱いになる: ブラウザのCookieを消す利用者は、毎回通知を受けることになります。通知文に理由を書いておくと、問い合わせを減らせます
  • 追加認証は2段階認証と組み合わせる: 通知だけでは侵入そのものは防げません。管理者には2段階認証を必須にし、通知はその上に重ねる仕組みとして位置づけます
  • パスワード変更時の扱い: 乗っ取りが疑われるときは、パスワード変更と同時にユーザーメタ mysite_known_devices を削除すると、登録済みの端末をすべて「未確認」に戻せます

まとめ

  • リスクベース認証は、端末・失敗回数・権限などのシグナルで危険度を評価し、危険なときだけ追加の確認や通知を行う方法
  • 導入は「通知」から始め、ログを見ながら追加認証や拒否へ段階的に進めると、正規の利用者を締め出す事故を防げる
  • 手軽なのは2段階認証プラグインの権限別必須化や信頼済み端末の機能。組織で使うサイトはIdPの条件付きアクセスも選択肢になる
  • 自作する場合は wp_login と wp_login_failed で新しい端末と失敗の連続を検知し、ランダムな値のCookieとハッシュで端末を識別する
  • 通知はメールの到達性とプライバシーポリシーの記載まで含めて整える

お気軽にご相談ください

お見積りへ お問い合わせへ
▲