Диагностика проблемы: зачем нужен автоматический OTP на странице входа
Стандартная форма входа WordPress уязвима к атакам перебора паролей (brute force) и фишинговым схемам. Добавление одноразового пароля (OTP) — эффективный способ повысить безопасность, но большинство готовых решений требуют установки плагинов, что не всегда приемлемо по причинам производительности или совместимости. В этом руководстве рассмотрим, как реализовать OTP-систему для входа на wp-login.php самостоятельно, используя собственный код.
Подготовка: какие данные нужны и как хранить OTP
Для генерации и проверки OTP нам потребуются:
- Генерация временного кода (6 цифр).
- Хранение временного кода и времени его жизни.
- Отправка кода пользователю (например, на email, SMS — для примера рассмотрим email).
- Валидация кода при входе.
Рекомендуется хранить OTP в user_meta с указанием времени создания, чтобы ограничить срок действия (например, 5 минут).
Пошаговое решение: реализация OTP без плагинов
1. Генерация и отправка OTP при вводе логина и пароля
Добавим проверку пароля, а вместо стандартного входа — генерацию кода и отправку его на email пользователя.
add_filter('authenticate', 'custom_authenticate_with_otp', 30, 3);
function custom_authenticate_with_otp($user, $username, $password) {
if (empty($username) || empty($password)) {
return $user;
}
$user = get_user_by('login', $username);
if (!$user || !wp_check_password($password, $user->user_pass, $user->ID)) {
return new WP_Error('authentication_failed', __('Неверный логин или пароль.'));
}
// Генерируем OTP
$otp = rand(100000, 999999);
update_user_meta($user->ID, 'login_otp_code', $otp);
update_user_meta($user->ID, 'login_otp_time', time());
// Отправляем OTP на email
wp_mail($user->user_email, 'Ваш одноразовый пароль для входа', 'Код: ' . $otp . '\nСрок действия 5 минут.');
// Сообщаем, что нужен OTP
return new WP_Error('otp_required', __('Введите одноразовый пароль, отправленный на ваш email.'));
}2. Добавление поля для ввода OTP на форму входа
Выведем дополнительное поле на страницу входа для ввода кода.
add_action('login_form', 'add_otp_field_to_login_form');
function add_otp_field_to_login_form() {
$value = isset($_POST['login_otp']) ? esc_attr($_POST['login_otp']) : '';
echo '<p><label for="login_otp">Одноразовый пароль (OTP)<br></label>';
echo '<input type="text" name="login_otp" id="login_otp" class="input" value="' . $value . '" size="20" /></p>';
}3. Проверка OTP при повторной отправке формы
Если ошибка otp_required была выдана, при повторной отправке формы проверяем введённый код.
add_filter('authenticate', 'check_otp_after_password', 40, 3);
function check_otp_after_password($user, $username, $password) {
if (is_wp_error($user) && $user->get_error_code() === 'otp_required') {
// Получаем пользователя по логину
$user = get_user_by('login', $username);
if (!$user) {
return new WP_Error('invalid_user', __('Пользователь не найден.'));
}
if (empty($_POST['login_otp'])) {
return new WP_Error('empty_otp', __('Пожалуйста, введите одноразовый пароль.'));
}
$otp_stored = get_user_meta($user->ID, 'login_otp_code', true);
$otp_time = get_user_meta($user->ID, 'login_otp_time', true);
$otp_input = sanitize_text_field($_POST['login_otp']);
if (!$otp_stored || !$otp_time || (time() - $otp_time > 300)) {
return new WP_Error('otp_expired', __('Срок действия одноразового пароля истёк. Попробуйте войти заново.'));
}
if ($otp_input !== $otp_stored) {
return new WP_Error('otp_invalid', __('Неверный одноразовый пароль.'));
}
// Удаляем OTP после успешной проверки
delete_user_meta($user->ID, 'login_otp_code');
delete_user_meta($user->ID, 'login_otp_time');
return $user; // Успешная аутентификация
}
return $user;
}Проверка результата после внедрения
- Попробуйте войти с правильным логином и паролем: вместо входа появится сообщение о необходимости ввести OTP.
- Проверьте почту пользователя — должен прийти код из 6 цифр.
- Введите код в дополнительное поле — вход должен пройти успешно.
- Если код неверен или истёк, должна появиться соответствующая ошибка.
- При повторном входе без ввода кода или с неправильным кодом доступ должен быть запрещён.
Частые ошибки и как их исправить
- OTP не приходит на email: проверьте корректность настроек SMTP на сервере и наличие функции
wp_mail(). Для отладки используйте плагин WP Mail Logging. - Ошибка «otp_required» не выводится: убедитесь, что фильтры
authenticateподключены с правильным приоритетом (30 и 40 соответственно). - Одноразовый пароль принимается всегда: проверьте, что в коде реализена проверка времени жизни OTP (не более 5 минут).
- Форма входа не отображает поле для OTP: проверьте, что хук
login_formдобавлен правильно и не конфликтует с темой.
Практические советы по безопасности и производительности
- Используйте HTTPS для передачи данных формы входа, чтобы защитить OTP от перехвата.
- Ограничьте количество попыток ввода OTP, чтобы избежать перебора кода.
- Для массовых сайтов рассмотрите интеграцию с SMS-сервисом для отправки кода вместо email.
- Удаляйте OTP из
user_metaсразу после успешного входа, чтобы исключить повторное использование. - При необходимости расширьте функционал для поддержки повторной отправки OTP с задержкой (например, 30 секунд между отправками).
Сравнение вариантов реализации OTP в WordPress
| Вариант | Плюсы | Минусы | Пример |
|---|---|---|---|
| Плагин (например, WP 2FA) | Быстро, поддержка, обновления | Нагрузка, конфликт с другими плагинами | WP 2FA |
| Собственный код (описанный выше) | Легковесно, гибко, без сторонних зависимостей | Требует навыков, поддержка на вас | Код из статьи |
| Внешняя интеграция (SMS, OAuth) | Максимальная безопасность | Сложность, дополнительные расходы | Интеграция API |