2026年5月28日
2026年5月28日
WordPressに二要素認証(2FA)を実装する方法
はじめに
「WordPressの管理者アカウントがパスワードだけで保護されていて不安」「不正ログイン試行が多くセキュリティを強化したい」「Google AuthenticatorやAuthyなどのアプリでWordPressにログインしたい」——二要素認証(2FA)でパスワード漏洩後の不正ログインを防げます。
症状・原因
パスワードだけの認証は辞書攻撃・ブルートフォース攻撃・フィッシングで突破されるリスクがあります。TOTPベースの2FAはパスワードに加えて時刻同期の6桁コードを要求するため、パスワードが漏洩しても不正ログインを防止できます。
解決手順
ステップ1:TOTPシークレットキーを生成してユーザーに紐付ける
// composer.json で TOTP ライブラリを追加
// composer require sonata-project/google-authenticator
// または OTPHP: composer require spomky-labs/otphp
// functions.php: シークレットキーの生成と保存
function generate_totp_secret(): string {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; // Base32文字
$secret = '';
for ( $i = 0; $i < 16; $i++ ) {
$secret .= $chars[ random_int( 0, 31 ) ];
}
return $secret;
}
// ユーザーの2FA設定を保存
function save_2fa_secret( int $user_id, string $secret ): void {
update_user_meta( $user_id, '_2fa_secret', $secret );
update_user_meta( $user_id, '_2fa_enabled', false ); // 確認後にtrueに
}
// ユーザープロファイルページに2FA設定フォームを追加
add_action( 'show_user_profile', 'render_2fa_settings' );
add_action( 'edit_user_profile', 'render_2fa_settings' );
function render_2fa_settings( WP_User $user ): void {
$secret = get_user_meta( $user->ID, '_2fa_secret', true );
$enabled = (bool) get_user_meta( $user->ID, '_2fa_enabled', true );
if ( ! $secret ) {
$secret = generate_totp_secret();
save_2fa_secret( $user->ID, $secret );
}
$site_name = rawurlencode( get_bloginfo( 'name' ) );
$email = rawurlencode( $user->user_email );
$otpauth = "otpauth://totp/{$site_name}:{$email}?secret={$secret}&issuer={$site_name}";
?>
<h2>二要素認証(2FA)</h2>
<table class="form-table">
<tr>
<th>状態</th>
<td><?php echo $enabled ? '<strong style="color:green">有効</strong>' : '<strong style="color:red">無効</strong>'; ?></td>
</tr>
<tr>
<th>QRコード</th>
<td>
<div id="qr-code-2fa"></div>
<p class="description">Google Authenticator / Authy でスキャン</p>
<script>
// QRコード表示(qrcode.js を使用)
document.addEventListener('DOMContentLoaded', function() {
if (typeof QRCode !== 'undefined') {
new QRCode(document.getElementById('qr-code-2fa'), {
text: '<?php echo esc_js( $otpauth ); ?>',
width: 200, height: 200
});
}
});
</script>
</td>
</tr>
<tr>
<th>確認コード</th>
<td>
<input type="text" name="2fa_verify_code" maxlength="6" placeholder="6桁のコード">
<?php wp_nonce_field( '2fa_setup_' . $user->ID, '2fa_nonce' ); ?>
</td>
</tr>
</table>
<?php
}
ステップ2:TOTPコードを検証する
// TOTP検証関数(外部ライブラリなしの実装)
function verify_totp( string $secret, string $code, int $discrepancy = 1 ): bool {
$code = preg_replace( '/\s/', '', $code );
if ( strlen( $code ) !== 6 || ! ctype_digit( $code ) ) {
return false;
}
$timestamp = (int) floor( time() / 30 );
for ( $i = -$discrepancy; $i <= $discrepancy; $i++ ) {
if ( hash_equals( (string) generate_totp_code( $secret, $timestamp + $i ), $code ) ) {
return true;
}
}
return false;
}
function generate_totp_code( string $secret, int $counter ): string {
// Base32デコード
$base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$secret = strtoupper( $secret );
$binary = '';
$buffer = 0;
$bits_left = 0;
for ( $i = 0; $i < strlen( $secret ); $i++ ) {
$pos = strpos( $base32chars, $secret[ $i ] );
if ( false === $pos ) continue;
$buffer = ( $buffer << 5 ) | $pos;
$bits_left += 5;
if ( $bits_left >= 8 ) {
$binary .= chr( ( $buffer >> ( $bits_left - 8 ) ) & 0xFF );
$bits_left -= 8;
}
}
// HMAC-SHA1
$time = pack( 'N*', 0 ) . pack( 'N*', $counter );
$hash = hash_hmac( 'sha1', $time, $binary, true );
$offset = ord( $hash[19] ) & 0xF;
$code = (
( ord( $hash[ $offset ] ) & 0x7F ) << 24 |
( ord( $hash[ $offset + 1 ] ) & 0xFF ) << 16 |
( ord( $hash[ $offset + 2 ] ) & 0xFF ) << 8 |
( ord( $hash[ $offset + 3 ] ) & 0xFF )
) % 1000000;
return str_pad( (string) $code, 6, '0', STR_PAD_LEFT );
}
// プロファイル保存時に2FA有効化
add_action( 'personal_options_update', 'save_2fa_profile' );
add_action( 'edit_user_profile_update', 'save_2fa_profile' );
function save_2fa_profile( int $user_id ): void {
if ( ! isset( $_POST['2fa_nonce'] ) ) return;
if ( ! wp_verify_nonce( $_POST['2fa_nonce'], '2fa_setup_' . $user_id ) ) return;
if ( ! current_user_can( 'edit_user', $user_id ) ) return;
$code = sanitize_text_field( $_POST['2fa_verify_code'] ?? '' );
$secret = get_user_meta( $user_id, '_2fa_secret', true );
if ( $code && $secret && verify_totp( $secret, $code ) ) {
update_user_meta( $user_id, '_2fa_enabled', true );
// バックアップコードを生成
$backup_codes = generate_backup_codes( $user_id );
add_settings_error( '2fa', '2fa_enabled', '2FAを有効化しました。バックアップコード: ' . implode( ', ', $backup_codes ), 'success' );
}
}
ステップ3:ログインフローに2FA検証を追加する
// ログイン成功後に2FAコード入力を要求
add_filter( 'authenticate', function( $user, string $username, string $password ) {
if ( is_wp_error( $user ) || ! ( $user instanceof WP_User ) ) {
return $user;
}
$enabled = (bool) get_user_meta( $user->ID, '_2fa_enabled', true );
if ( ! $enabled ) {
return $user; // 2FA無効ならそのままログイン
}
// セッションにユーザーIDを一時保存して2FAページにリダイレクト
if ( ! session_id() ) session_start();
$_SESSION['2fa_pending_user'] = $user->ID;
$_SESSION['2fa_timestamp'] = time();
// 2FAコード入力ページにリダイレクト(後でwp_loginフックで処理)
wp_redirect( wp_login_url() . '?action=2fa' );
exit;
}, 30, 3 );
// 2FAコード入力フォームの処理
add_action( 'login_form_2fa', function(): void {
if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) {
if ( ! session_id() ) session_start();
$pending_id = $_SESSION['2fa_pending_user'] ?? 0;
$timestamp = $_SESSION['2fa_timestamp'] ?? 0;
// セッション有効期限(5分)
if ( ! $pending_id || time() - $timestamp > 300 ) {
wp_redirect( wp_login_url() );
exit;
}
$code = sanitize_text_field( $_POST['2fa_code'] ?? '' );
$secret = get_user_meta( $pending_id, '_2fa_secret', true );
if ( verify_totp( $secret, $code ) || verify_backup_code( $pending_id, $code ) ) {
unset( $_SESSION['2fa_pending_user'], $_SESSION['2fa_timestamp'] );
$user = get_user_by( 'id', $pending_id );
wp_set_auth_cookie( $user->ID, false );
wp_redirect( admin_url() );
exit;
}
// 失敗
wp_redirect( wp_login_url() . '?action=2fa&error=1' );
exit;
}
} );
ステップ4:バックアップコードを生成・検証する
function generate_backup_codes( int $user_id, int $count = 8 ): array {
$codes = [];
$hashed_codes = [];
for ( $i = 0; $i < $count; $i++ ) {
$code = strtoupper( bin2hex( random_bytes( 4 ) ) ); // 8文字
$codes[] = $code;
$hashed_codes[] = wp_hash_password( $code );
}
update_user_meta( $user_id, '_2fa_backup_codes', $hashed_codes );
return $codes; // 平文は一度だけ表示
}
function verify_backup_code( int $user_id, string $code ): bool {
$code = strtoupper( preg_replace( '/[^A-Z0-9]/', '', $code ) );
$stored_codes = get_user_meta( $user_id, '_2fa_backup_codes', true );
if ( ! is_array( $stored_codes ) ) return false;
foreach ( $stored_codes as $index => $hashed ) {
if ( wp_check_password( $code, $hashed ) ) {
// 使用済みコードを削除
unset( $stored_codes[ $index ] );
update_user_meta( $user_id, '_2fa_backup_codes', array_values( $stored_codes ) );
return true;
}
}
return false;
}
ステップ5:Two Factor プラグインを使う場合
# 公式の Two Factor プラグインを使う(推奨)
wp plugin install two-factor --activate
# 管理者ユーザーに2FAを強制
wp user meta update 1 _two_factor_provider "Two_Factor_Totp"
# 2FAを無効化(ロックアウト時の緊急対応)
wp user meta delete 1 _two_factor_enabled_providers
wp user meta delete 1 _two_factor_provider
// Two Factor プラグイン使用時: 管理者ロールに2FAを強制
add_filter( 'two_factor_enabled_providers_for_user', function(
array $providers,
WP_User $user
): array {
// 管理者には2FAを必須化
if ( in_array( 'administrator', $user->roles, true ) && empty( $providers ) ) {
// プロバイダーが設定されていない場合はメール2FAにフォールバック
return [ 'Two_Factor_Email' => 'Two_Factor_Email' ];
}
return $providers;
}, 10, 2 );
注意事項
- 2FAを設定した後、自分自身がロックアウトされないようにバックアップコードを必ず安全な場所に保存してください。ロックアウト時はWP-CLIまたはデータベース直接操作でリセットできます。
- TOTPは時刻同期に依存します。サーバーの時刻がずれると認証が失敗します。
ntpdまたはchronyでサーバー時刻を正確に保ってください。 - カスタム実装よりTwo Factor公式プラグインの使用を推奨します。TOTP・メール・WebAuthn(パスキー)などに対応しています。
まとめ
2FA実装は「TOTPシークレット生成→ユーザーメタに保存→QRコードをプロファイルページに表示→HMAC-SHA1でTOTPコードを検証→authenticateフィルターでログインフローに挿入→バックアップコードをwp_hash_passwordでハッシュ保存→緊急時はWP-CLIでリセット」の流れで整備します。関連記事:WordPressにセキュリティヘッダーを設定する方法、WordPressのXML-RPC攻撃対策をする方法。