Двухфакторная аутентификация для всех пользователей WordPress без плагинов: готовый пример - Компьюти
Logo КОМПЬЮТИ

Двухфакторная аутентификация для всех пользователей WordPress без плагинов: готовый пример

Время на прочтение: 10 мин.

Двухфакторная аутентификация (2FA) — один из самых простых способов существенно повысить безопасность учётных записей WordPress. Даже если пароль пользователя украден, злоумышленнику дополнительно понадобится одноразовый код из приложения-аутентификатора.

В WordPress необязательно устанавливать отдельный плагин для реализации 2FA. Систему можно встроить непосредственно в код сайта: добавить поле TOTP-кода в стандартную форму входа, сохранить секрет пользователя в user_meta и проверять одноразовый пароль на этапе аутентификации.

Ниже — полностью самостоятельный пример на PHP без сторонней библиотеки для TOTP.

Важно: пример предназначен прежде всего для разработчиков. Перед установкой на рабочий сайт обязательно сделайте резервную копию и проверьте механизм на тестовой копии сайта.

Что мы будем делать

В результате получим следующую схему:

Логин + пароль + одноразовый код
              ↓
       WordPress проверяет
       логин и пароль
              ↓
       Проверка TOTP-кода
              ↓
       Успешная аутентификация
              ↓
       WordPress создаёт cookie

Это важно: WordPress сначала проверяет пароль и TOTP, и только после успешной аутентификации создаёт полноценную сессию. В штатном процессе wp_signon() сначала вызывает wp_authenticate(), а затем wp_set_auth_cookie(), поэтому фильтр authenticate подходит для дополнительной проверки второго фактора.

Сам WordPress предоставляет authenticate для дополнительной проверки учётных данных, а login_form позволяет добавить дополнительные поля в стандартную форму входа.


1. Что понадобится

Нам понадобится:

  • WordPress;
  • PHP 7.4+;
  • HTTPS;
  • доступ к functions.php дочерней темы;
  • приложение-аутентификатор: Google Authenticator, Microsoft Authenticator, 1Password и т. п.

Для примера мы не будем устанавливать Composer-пакет или WordPress-плагин. Алгоритм TOTP реализуем непосредственно на PHP.

Это сделано специально для демонстрации принципа. В коммерческом проекте криптографическую реализацию лучше брать из проверенной библиотеки и регулярно обновлять её.


2. Полный код

Создайте резервную копию сайта и откройте:

/wp-content/themes/ВАША-ДОЧЕРНЯЯ-ТЕМА/functions.php

Добавьте следующий код.

<?php

/**
 * Simple WordPress TOTP 2FA
 *
 * Без стороннего WordPress-плагина.
 *
 * Что делает:
 * - добавляет поле TOTP в форму wp-login.php;
 * - генерирует секрет;
 * - проверяет TOTP;
 * - не позволяет войти без 2FA;
 * - предоставляет страницу настройки 2FA;
 *
 * ВАЖНО:
 * Это демонстрационный production-oriented пример.
 * Перед использованием на критическом проекте проведите аудит.
 */


/* =========================================================
 * НАСТРОЙКИ
 * ======================================================= */

define( 'MY_2FA_META_SECRET', '_my_2fa_secret' );
define( 'MY_2FA_META_ENABLED', '_my_2fa_enabled' );


/* =========================================================
 * BASE32
 * ======================================================= */

/**
 * Генерация Base32-секрета.
 */
function my_2fa_base32_encode( $data ) {

    $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
    $binary   = '';

    foreach ( str_split( $data ) as $char ) {
        $binary .= str_pad(
            decbin( ord( $char ) ),
            8,
            '0',
            STR_PAD_LEFT
        );
    }

    $output = '';
    $length = strlen( $binary );

    for ( $i = 0; $i < $length; $i += 5 ) {

        $chunk = substr( $binary, $i, 5 );

        if ( strlen( $chunk ) < 5 ) {
            $chunk = str_pad( $chunk, 5, '0' );
        }

        $output .= $alphabet[ bindec( $chunk ) ];
    }

    return $output;
}


/**
 * Декодирование Base32.
 */
function my_2fa_base32_decode( $input ) {

    $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';

    $input = strtoupper( $input );
    $input = preg_replace( '/[^A-Z2-7]/', '', $input );

    $binary = '';

    for ( $i = 0; $i < strlen( $input ); $i++ ) {

        $position = strpos(
            $alphabet,
            $input[ $i ]
        );

        if ( false === $position ) {
            continue;
        }

        $binary .= str_pad(
            decbin( $position ),
            5,
            '0',
            STR_PAD_LEFT
        );
    }

    $output = '';

    for ( $i = 0; $i + 8 <= strlen( $binary ); $i += 8 ) {

        $output .= chr(
            bindec(
                substr( $binary, $i, 8 )
            )
        );
    }

    return $output;
}


/* =========================================================
 * TOTP
 * ======================================================= */

/**
 * Генерация 6-значного TOTP-кода.
 */
function my_2fa_totp( $secret, $timestamp = null ) {

    if ( null === $timestamp ) {
        $timestamp = time();
    }

    $counter = (int) floor( $timestamp / 30 );

    $binary_counter = pack(
        'N2',
        0,
        $counter
    );

    $secret_binary = my_2fa_base32_decode( $secret );

    $hash = hash_hmac(
        'sha1',
        $binary_counter,
        $secret_binary,
        true
    );

    $offset = ord(
        substr( $hash, -1 )
    ) & 0x0F;

    $binary_code = substr(
        $hash,
        $offset,
        4
    );

    $value = unpack(
        'N',
        $binary_code
    );

    $value = $value[1] & 0x7FFFFFFF;

    return str_pad(
        (string) ( $value % 1000000 ),
        6,
        '0',
        STR_PAD_LEFT
    );
}


/**
 * Проверка TOTP.
 *
 * Разрешаем небольшое расхождение времени:
 * предыдущий, текущий и следующий 30-секундный интервал.
 */
function my_2fa_verify_totp( $secret, $code ) {

    $code = preg_replace( '/\D/', '', $code );

    if ( 6 !== strlen( $code ) ) {
        return false;
    }

    $time = time();

    for ( $offset = -1; $offset <= 1; $offset++ ) {

        $expected = my_2fa_totp(
            $secret,
            $time + ( $offset * 30 )
        );

        if ( hash_equals( $expected, $code ) ) {
            return true;
        }
    }

    return false;
}


/* =========================================================
 * ГЕНЕРАЦИЯ СЕКРЕТА
 * ======================================================= */

function my_2fa_generate_secret() {

    return my_2fa_base32_encode(
        random_bytes( 20 )
    );
}


/* =========================================================
 * OT AUTH URI
 * ======================================================= */

/**
 * URI, который можно использовать для создания QR-кода.
 */
function my_2fa_get_otpauth_uri( $user, $secret ) {

    $issuer = wp_parse_url(
        home_url(),
        PHP_URL_HOST
    );

    $issuer = $issuer ?: 'WordPress';

    $label = rawurlencode(
        $issuer . ':' . $user->user_email
    );

    return sprintf(
        'otpauth://totp/%s?secret=%s&issuer=%s',
        $label,
        rawurlencode( $secret ),
        rawurlencode( $issuer )
    );
}


/* =========================================================
 * ДОБАВЛЯЕМ ПОЛЕ В СТАНДАРТНУЮ ФОРМУ ВХОДА
 * ======================================================= */

add_action(
    'login_form',
    function () {

        ?>

        <p>
            <label for="my_2fa_code">
                Код двухфакторной аутентификации<br>

                <input
                    type="text"
                    name="my_2fa_code"
                    id="my_2fa_code"
                    class="input"
                    value=""
                    size="20"
                    inputmode="numeric"
                    autocomplete="one-time-code"
                    pattern="[0-9]{6}"
                    maxlength="6"
                >

            </label>
        </p>

        <?php
    }
);


/* =========================================================
 * ПРОВЕРКА 2FA ПРИ ВХОДЕ
 * ======================================================= */

add_filter(
    'authenticate',
    function ( $user, $username, $password ) {

        /*
         * Если предыдущая проверка уже завершилась ошибкой,
         * ничего не меняем.
         */
        if ( is_wp_error( $user ) ) {
            return $user;
        }

        /*
         * Если логин/пароль ещё не прошли проверку,
         * не вмешиваемся.
         */
        if ( ! $user instanceof WP_User ) {
            return $user;
        }

        /*
         * Получаем секрет пользователя.
         */
        $secret = get_user_meta(
            $user->ID,
            MY_2FA_META_SECRET,
            true
        );

        /*
         * 2FA обязательна для всех пользователей,
         * поэтому отсутствие секрета блокирует вход.
         */
        if ( empty( $secret ) ) {

            return new WP_Error(
                '2fa_not_configured',
                sprintf(
                    '<strong>Ошибка:</strong> Для пользователя %s не настроена двухфакторная аутентификация.',
                    esc_html( $user->user_login )
                )
            );
        }

        /*
         * Получаем код из формы.
         */
        $code = isset( $_POST['my_2fa_code'] )
            ? wp_unslash( $_POST['my_2fa_code'] )
            : '';

        /*
         * Проверяем код.
         */
        if ( ! my_2fa_verify_totp( $secret, $code ) ) {

            return new WP_Error(
                'invalid_2fa',
                '<strong>Ошибка:</strong> Неверный код двухфакторной аутентификации.'
            );
        }

        return $user;

    },
    30,
    3
);


/* =========================================================
 * СТРАНИЦА НАСТРОЙКИ 2FA
 * ======================================================= */

add_action(
    'admin_menu',
    function () {

        add_users_page(
            'Двухфакторная аутентификация',
            'Моя 2FA',
            'read',
            'my-2fa',
            'my_2fa_settings_page'
        );

    }
);


/* =========================================================
 * СТРАНИЦА НАСТРОЙКИ
 * ======================================================= */

function my_2fa_settings_page() {

    if ( ! is_user_logged_in() ) {
        return;
    }

    $user_id = get_current_user_id();
    $user    = wp_get_current_user();

    /*
     * Создаём секрет, если его ещё нет.
     */
    $secret = get_user_meta(
        $user_id,
        MY_2FA_META_SECRET,
        true
    );

    if ( empty( $secret ) ) {

        $secret = my_2fa_generate_secret();

        update_user_meta(
            $user_id,
            MY_2FA_META_SECRET,
            $secret
        );
    }

    $enabled = (bool) get_user_meta(
        $user_id,
        MY_2FA_META_ENABLED,
        true
    );

    /*
     * Проверка первого кода.
     */
    if (
        isset( $_POST['my_2fa_enable'] )
        && check_admin_referer( 'my_2fa_enable' )
    ) {

        $code = isset( $_POST['my_2fa_setup_code'] )
            ? wp_unslash( $_POST['my_2fa_setup_code'] )
            : '';

        if ( my_2fa_verify_totp( $secret, $code ) ) {

            update_user_meta(
                $user_id,
                MY_2FA_META_ENABLED,
                1
            );

            $enabled = true;

            echo '<div class="notice notice-success"><p>2FA успешно включена.</p></div>';

        } else {

            echo '<div class="notice notice-error"><p>Неверный код. Проверьте время на телефоне и попробуйте ещё раз.</p></div>';
        }
    }

    /*
     * Сброс 2FA.
     */
    if (
        isset( $_POST['my_2fa_reset'] )
        && check_admin_referer( 'my_2fa_reset' )
    ) {

        /*
         * Не удаляем секрет автоматически.
         * Вместо этого создаём новый.
         */
        $secret = my_2fa_generate_secret();

        update_user_meta(
            $user_id,
            MY_2FA_META_SECRET,
            $secret
        );

        update_user_meta(
            $user_id,
            MY_2FA_META_ENABLED,
            0
        );

        $enabled = false;

        echo '<div class="notice notice-warning"><p>Старый секрет сброшен. Настройте 2FA заново.</p></div>';
    }

    $uri = my_2fa_get_otpauth_uri(
        $user,
        $secret
    );

    ?>

    <div class="wrap">

        <h1>Двухфакторная аутентификация</h1>

        <?php if ( $enabled ) : ?>

            <div class="notice notice-success">
                <p>
                    <strong>2FA включена.</strong>
                    При следующем входе потребуется код из приложения-аутентификатора.
                </p>
            </div>

            <form method="post">

                <?php wp_nonce_field( 'my_2fa_reset' ); ?>

                <input
                    type="hidden"
                    name="my_2fa_reset"
                    value="1"
                >

                <?php
                submit_button(
                    'Перегенерировать секрет',
                    'secondary'
                );
                ?>

            </form>

        <?php else : ?>

            <p>
                Откройте приложение-аутентификатор
                и добавьте новую учётную запись.
            </p>

            <p>
                Если приложение поддерживает ручной ввод,
                используйте следующий секрет:
            </p>

            <p>
                <code>
                    <?php echo esc_html( $secret ); ?>
                </code>
            </p>

            <p>
                Также можно использовать URI:
            </p>

            <p>
                <code style="word-break:break-all;">
                    <?php echo esc_html( $uri ); ?>
                </code>
            </p>

            <hr>

            <h2>Проверка настройки</h2>

            <p>
                Введите шестизначный код, который сейчас показывает приложение.
            </p>

            <form method="post">

                <?php wp_nonce_field( 'my_2fa_enable' ); ?>

                <p>
                    <input
                        type="text"
                        name="my_2fa_setup_code"
                        inputmode="numeric"
                        pattern="[0-9]{6}"
                        maxlength="6"
                        autocomplete="one-time-code"
                    >
                </p>

                <input
                    type="hidden"
                    name="my_2fa_enable"
                    value="1"
                >

                <?php
                submit_button(
                    'Включить 2FA'
                );
                ?>

            </form>

        <?php endif; ?>

    </div>

    <?php
}

3. Как работает этот код

Разберём основные части.

Генерация секрета

При первоначальной настройке создаётся случайный 20-байтный секрет:

$secret = my_2fa_generate_secret();

Внутри используется:

random_bytes(20)

Полученный бинарный массив преобразуется в Base32.

Например, пользователь может получить:

JBSWY3DPEHPK3PXP

Именно этот секрет должен знать только сервер и приложение-аутентификатор пользователя.


4. Как формируется TOTP

TOTP основан на общем секрете и текущем времени.

В нашем примере используется интервал 30 секунд:

$counter = (int) floor( $timestamp / 30 );

Затем рассчитывается HMAC-SHA1:

$hash = hash_hmac(
    'sha1',
    $binary_counter,
    $secret_binary,
    true
);

После этого из результата получается шестизначное число.

То есть приложение-аутентификатор и WordPress независимо выполняют один и тот же алгоритм:

секрет + текущее время
          ↓
       HMAC-SHA1
          ↓
     6-значный код

5. Почему разрешены три временных интервала

В коде есть:

for ( $offset = -1; $offset <= 1; $offset++ ) {

Это означает, что сервер проверяет:

предыдущий интервал
текущий интервал
следующий интервал

Каждый интервал длится 30 секунд.

Это компенсирует небольшое расхождение часов между сервером и смартфоном.

При этом мы не разрешаем слишком большое временное окно, поскольку это увеличило бы период действия каждого кода.


6. Добавление поля в форму WordPress

WordPress предоставляет хук login_form, который срабатывает после поля пароля стандартной формы входа. Именно туда мы добавляем поле TOTP.

Получается стандартная форма:

Имя пользователя
[________________]

Пароль
[________________]

Код двухфакторной аутентификации
[________________]

[ Войти ]

Пользователь теперь вводит три значения:

логин
пароль
6-значный код

7. Проверка второго фактора

Ключевая часть находится здесь:

add_filter(
    'authenticate',
    function ( $user, $username, $password ) {

WordPress передаёт результат проверки логина и пароля в фильтр authenticate. Если пользователь уже успешно определён как WP_User, мы можем проверить второй фактор. WordPress прямо документирует authenticate как механизм для дополнительной проверки аутентификации.

Получаем секрет:

$secret = get_user_meta(
    $user->ID,
    MY_2FA_META_SECRET,
    true
);

user_meta — штатный механизм WordPress для хранения дополнительных данных пользователя.

После этого проверяем код:

if ( ! my_2fa_verify_totp( $secret, $code ) ) {

    return new WP_Error(
        'invalid_2fa',
        '<strong>Ошибка:</strong> Неверный код двухфакторной аутентификации.'
    );
}

Возвращение WP_Error останавливает успешную аутентификацию. Это принципиально важно.


Внутри стандартного процесса WordPress wp_signon() сначала получает результат wp_authenticate(), и только если он не является ошибкой, вызывает wp_set_auth_cookie().

Получается:

POST /wp-login.php
        ↓
логин + пароль
        ↓
wp_authenticate()
        ↓
проверка TOTP
        ↓
WP_User
        ↓
wp_set_auth_cookie()
        ↓
пользователь вошёл

Если TOTP неправильный:

POST /wp-login.php
        ↓
логин + пароль
        ↓
wp_authenticate()
        ↓
TOTP НЕВЕРНЫЙ
        ↓
WP_Error
        ↓
cookie НЕ создаётся

Именно поэтому нельзя просто проверять 2FA после события wp_login: на этом этапе пользователь уже успешно вошёл.


9. Первоначальная настройка

После добавления кода войдите в WordPress под нужным пользователем и откройте:

Пользователи → Моя 2FA

Страница создаст секрет.

Например:

JBSWY3DPEHPK3PXP

В приложении-аутентификаторе создайте новую запись и введите этот секрет вручную.

После этого приложение начнёт показывать:

123456

Введите текущий код на странице WordPress и нажмите «Включить 2FA».

После успешной проверки пользователь получает:

_totp_secret  = секрет
_totp_enabled = 1

Для обработки формы настройки используется WordPress nonce. WordPress рекомендует применять nonce в формах для защиты от подделки запросов.


10. Важная проблема: как включить 2FA сразу для всех

Здесь есть важный нюанс.

В приведённом примере 2FA обязательна для каждого пользователя, у которого происходит вход. Если у пользователя нет _my_2fa_secret, вход будет запрещён:

if ( empty( $secret ) ) {

    return new WP_Error(
        '2fa_not_configured',
        'Для этой учётной записи не настроена двухфакторная аутентификация.'
    );
}

Поэтому нельзя просто установить код на рабочий сайт и выйти из всех аккаунтов.

Сначала пользователи должны зарегистрировать свои секреты.

Для существующего сайта лучше внедрять защиту поэтапно.

Вариант 1 — период настройки

На первом этапе разрешить вход пользователям без 2FA и показать им уведомление:

Ваша учётная запись ещё не защищена 2FA.
Настройте двухфакторную аутентификацию.

После окончания переходного периода сделать наличие секрета обязательным.

Вариант 2 — администратор заранее создаёт секреты

Можно написать отдельную административную страницу, которая генерирует секрет для каждого пользователя.

Но передавать секреты пользователям нужно безопасным способом.

Вариант 3 — обязательная настройка после входа

Более удобный вариант — разрешить пользователю войти один раз, но не давать ему доступ к сайту до завершения настройки 2FA.

Это требует дополнительной логики промежуточной сессии и уже является более серьёзной модификацией процесса авторизации.


11. Как добавить QR-код

В примере намеренно не используется сторонний QR-библиотека.

Мы генерируем стандартный URI:

otpauth://totp/...

Например:

otpauth://totp/example.com:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=example.com

QR-код можно генерировать любой совместимой библиотекой или сервисом, который вы контролируете.

В production-системе я бы не рекомендовал отправлять секрет на случайный внешний QR-сервис: секрет является вторым фактором и должен оставаться конфиденциальным.

Поэтому для полноценной реализации лучше использовать локальную PHP-библиотеку генерации QR-кодов.


12. Где хранится секрет

В нашем варианте секрет хранится в:

wp_usermeta

с ключом:

_my_2fa_secret

То есть база содержит примерно:

user_id: 15
meta_key: _my_2fa_secret
meta_value: JBSWY3DPEHPK3PXP

Это рабочий вариант, но для особо критичных систем можно пойти дальше и шифровать секреты перед сохранением.

Самое главное — не выводить секрет в логи, debug-вывод, REST API или административные страницы, доступные другим пользователям.


13. Почему нельзя хешировать TOTP-секрет как пароль

Пароли WordPress можно хранить в виде одностороннего хеша.

TOTP-секрет устроен иначе.

Серверу необходимо иметь возможность использовать исходный секрет для вычисления:

HMAC(secret, time)

Поэтому обычный password_hash() здесь не подходит.

password_hash() предназначен для создания одностороннего хеша пароля, а TOTP требует возможности использовать секрет для генерации кодов.

Если требуется более высокий уровень защиты, секрет можно шифровать, а не просто хешировать.


14. Почему используется hash_equals()

При проверке кода мы используем:

hash_equals(
    $expected,
    $code
)

а не:

$expected === $code

hash_equals() предназначен для сравнения секретных строк с защитой от timing attacks. PHP отдельно рекомендует передавать известное секретное значение первым аргументом, а пользовательское — вторым.

Поэтому правильный вариант:

hash_equals( $expected, $code );

15. Что ещё необходимо добавить для production

Предыдущий код показывает полноценный базовый механизм, но для серьёзного проекта я бы не останавливался на нём.

Защита от brute-force

Сейчас атакующий может отправлять много неправильных TOTP-кодов.

Нужно добавить rate limiting.

Например:

5 неправильных кодов
        ↓
блокировка на 5 минут

Причём желательно учитывать не только IP, но и пользователя.


Резервные коды

Пользователь может потерять смартфон.

Поэтому желательно добавить, например:

8J4K-2F9P
Q7XM-91KD
...

Каждый код должен использоваться только один раз.

Хранить резервные коды лучше в виде хешей, а не открытым текстом.


Защита HTTPS

Весь механизм должен работать через HTTPS.

WordPress рекомендует использовать защищённое соединение для входа, поскольку при обычном HTTP пароль и другие данные авторизации могут быть перехвачены.

Проверить сайт можно хотя бы по адресу:

https://example.com/wp-login.php

а не:

http://example.com/wp-login.php

16. Не забываем про REST API

Есть ещё одна распространённая ошибка.

Можно защитить:

/wp-login.php

и считать, что весь WordPress теперь защищён 2FA.

Это не так.

В зависимости от конфигурации сайта могут существовать:

  • REST API;
  • XML-RPC;
  • Application Passwords;
  • кастомные API;
  • сторонние интеграции;
  • отдельные формы авторизации.

WordPress сам использует различные механизмы проверки cookie и авторизации, поэтому каждый используемый способ доступа нужно рассматривать отдельно.

Особенно важно не сломать API-интеграции после введения обязательной 2FA.


17. Не используйте 2FA для Application Passwords автоматически

Application Password — это не обычный пароль пользователя, вводимый через /wp-login.php.

Например, стороннее приложение может обращаться к WordPress REST API следующим образом:

Application
     ↓
REST API
     ↓
Application Password

Оно не проходит через обычную HTML-форму входа.

Поэтому добавленный нами TOTP-код не будет автоматически защищать такие запросы.

Если сайт использует Application Passwords, их модель безопасности нужно проектировать отдельно.


18. Что произойдёт при обычном входе

После установки кода пользователь открывает:

/wp-login.php

и видит:

Имя пользователя
[ admin ]

Пароль
[ ******** ]

Код двухфакторной аутентификации
[ 123456 ]

[ Войти ]

При правильном пароле и правильном коде:

WP_User
   ↓
wp_set_auth_cookie()
   ↓
Авторизация

При неправильном TOTP:

WP_Error
   ↓
Авторизация остановлена
   ↓
Cookie не создаётся

19. Что будет, если злоумышленник знает пароль

Представим, что пароль администратора:

MyVerySecretPassword

стал известен злоумышленнику.

Без 2FA:

пароль → вход

С нашим механизмом:

пароль
   ↓
TOTP
   ↓
вход

Злоумышленнику дополнительно потребуется TOTP-секрет или доступ к приложению-аутентификатору.

Именно в этом заключается основной смысл двухфакторной аутентификации.


20. Важное замечание о безопасности самого кода

Самостоятельная реализация 2FA — это не то же самое, что установить зрелый 2FA-плагин.

У готового решения уже могут быть реализованы:

  • recovery codes;
  • rate limiting;
  • уведомления;
  • блокировки;
  • управление устройствами;
  • резервный способ входа;
  • WebAuthn/passkeys;
  • защита от различных сценариев обхода;
  • миграции данных;
  • совместимость с новыми версиями WordPress.

Поэтому собственный код имеет смысл, если вам действительно нужен контроль над реализацией.


21. Куда лучше помещать такой код

Технически пример можно разместить в:

functions.php

Но для безопасности я предпочёл бы отдельный файл:

wp-content/mu-plugins/my-2fa.php

Это не обычный плагин, который пользователь устанавливает через админку, а автоматически загружаемый must-use plugin.

Преимущество в том, что защита не исчезнет при смене или обновлении темы.

Если принципиально требуется вообще отсутствие любого plugin-кода, используйте functions.php дочерней темы.


22. Минимальный план внедрения

Для существующего сайта безопаснее делать внедрение поэтапно:

1. Резервная копия
       ↓
2. Тестовая копия сайта
       ↓
3. Установка кода
       ↓
4. Настройка 2FA администратора
       ↓
5. Проверка обычного входа
       ↓
6. Проверка неправильного TOTP
       ↓
7. Проверка выхода
       ↓
8. Проверка REST/API-интеграций
       ↓
9. Настройка остальных пользователей
       ↓
10. Принудительное включение 2FA

Не включайте обязательную 2FA на production-сайте до того, как проверите возможность восстановления доступа.


Заключение

Двухфакторную аутентификацию в WordPress действительно можно реализовать без стороннего плагина.

В базовом варианте достаточно:

  1. сгенерировать уникальный TOTP-секрет;
  2. сохранить его для пользователя;
  3. добавить поле к стандартной форме входа;
  4. проверить пароль через стандартный механизм WordPress;
  5. проверить TOTP;
  6. только после этого позволить WordPress создать authentication cookie.

WordPress специально предоставляет хуки для расширения процесса аутентификации, а wp_signon() создаёт authentication cookie только после успешного результата wp_authenticate().

Главное — не ограничиваться генерацией шестизначного кода. Для реального проекта необходимо также предусмотреть rate limiting, резервные коды, безопасное хранение TOTP-секретов, HTTPS, восстановление доступа и отдельную защиту API.

И ещё один практический момент: перед включением обязательной 2FA для всех пользователей обязательно оставьте себе гарантированный способ восстановления доступа. Ошибка в коде авторизации может заблокировать не только обычных пользователей, но и администратора сайта.


Читайте также:

Приглашаем поучаствовать в обсуждении статьи:

Комментариев 0