Диагностика задачи: зачем нужна авторизация через SMS OTP в WooCommerce
Безопасность пользовательских аккаунтов в WooCommerce — критический аспект для интернет-магазинов, особенно при работе с персональными данными и оплатой. Пароли можно украсть, забыть или подобрать. Внедрение одноразового пароля (OTP, One-Time Password) через SMS повышает безопасность, минимизирует риск взлома и упрощает вход для пользователей, не требуя запоминать сложные пароли.
Задача: реализовать в WooCommerce авторизацию пользователей по SMS OTP вместо стандартного пароля или как дополнительный фактор.
Как проверить, что в WooCommerce нужна SMS OTP авторизация
- Анализ обращений службы поддержки по вопросам взлома аккаунтов;
- Высокий процент забытых паролей и запросов на восстановление;
- Желание улучшить UX и безопасность без внедрения сложных двухфакторных систем;
- Проверка наличия плагинов для OTP и их возможностей. Если их нет — нужна разработка кастомного решения.
Пошаговое решение: реализация SMS OTP авторизации в WooCommerce
1. Выбор SMS-сервиса
Для отправки OTP понадобится SMS-шлюз (например, Twilio, Nexmo, или российские провайдеры). Регистрация и получение API-ключей обязательны. Ниже пример для Twilio (один из самых популярных международных сервисов):
require_once 'vendor/autoload.php';
use Twilio\Rest\Client;
function send_sms_otp($phone, $otp) {
$sid = 'TWILIO_SID';
$token = 'TWILIO_TOKEN';
$client = new Client($sid, $token);
try {
$message = $client->messages->create(
$phone,
[
'from' => 'TWILIO_PHONE_NUMBER',
'body' => "Ваш код подтверждения: $otp"
]
);
return true;
} catch (Exception $e) {
error_log('Ошибка отправки SMS: ' . $e->getMessage());
return false;
}
}2. Генерация и хранение OTP кода
Код OTP генерируется при попытке входа, сохраняется в сессии или transient с ограниченным временем жизни (например, 5 минут).
function generate_otp_code() {
return rand(100000, 999999);
}
function save_otp_for_user($user_id, $otp) {
set_transient('otp_code_' . $user_id, $otp, 5 * MINUTE_IN_SECONDS);
}
function get_saved_otp($user_id) {
return get_transient('otp_code_' . $user_id);
}3. Перехват стандартной авторизации WooCommerce
Подменяем стандартную форму входа на форму с номером телефона, отправляем OTP, а затем принимаем ввод кода для подтверждения.
Пример фильтра для кастомного логина:
add_action('woocommerce_login_form', 'custom_otp_login_form');
function custom_otp_login_form() {
?>
<p class="form-row form-row-wide">
<label for="otp_phone">Номер телефона <span class="required">*</span></label>
<input type="text" class="input-text" name="otp_phone" id="otp_phone" autocomplete="off" />
</p>
<p class="form-row form-row-wide">
<button type="button" id="send_otp_btn">Получить код</button>
</p>
<p class="form-row form-row-wide" style="display:none;" id="otp_input_field">
<label for="otp_code">Введите код <span class="required">*</span></label>
<input type="text" class="input-text" name="otp_code" id="otp_code" autocomplete="off" />
</p>
<p class="form-row form-row-wide" style="display:none;" id="otp_submit_field">
<button type="submit" class="button" name="otp_login">Войти</button>
</p>
<script>
jQuery(document).ready(function($){
$('#send_otp_btn').click(function(){
var phone = $('#otp_phone').val();
if(!phone) {
alert('Введите номер телефона');
return;
}
$.post(ajaxurl, {action: 'send_sms_otp', phone: phone}, function(response){
if(response.success) {
alert('Код отправлен');
$('#otp_input_field, #otp_submit_field').show();
$('#send_otp_btn').hide();
} else {
alert('Ошибка: ' + response.data);
}
});
});
});
</script>
<?php
}4. Обработка AJAX-запроса для отправки OTP
add_action('wp_ajax_send_sms_otp', 'ajax_send_sms_otp');
add_action('wp_ajax_nopriv_send_sms_otp', 'ajax_send_sms_otp');
function ajax_send_sms_otp() {
$phone = sanitize_text_field($_POST['phone']);
if (!$phone) {
wp_send_json_error('Номер телефона не указан');
}
// Поиск пользователя по номеру телефона (предполагается, что номер хранится в user meta 'billing_phone')
$user_query = new WP_User_Query(array(
'meta_key' => 'billing_phone',
'meta_value' => $phone,
'number' => 1
));
$users = $user_query->get_results();
if (empty($users)) {
wp_send_json_error('Пользователь с таким номером не найден');
}
$user = $users[0];
$otp = generate_otp_code();
save_otp_for_user($user->ID, $otp);
if(send_sms_otp($phone, $otp)) {
wp_send_json_success();
} else {
wp_send_json_error('Не удалось отправить SMS');
}
}5. Валидация OTP при отправке формы входа
add_action('init', 'process_otp_login');
function process_otp_login() {
if(isset($_POST['otp_login'])) {
$phone = sanitize_text_field($_POST['otp_phone']);
$code = sanitize_text_field($_POST['otp_code']);
if(!$phone || !$code) {
wc_add_notice('Введите номер телефона и код из SMS', 'error');
return;
}
$user_query = new WP_User_Query(array(
'meta_key' => 'billing_phone',
'meta_value' => $phone,
'number' => 1
));
$users = $user_query->get_results();
if(empty($users)) {
wc_add_notice('Пользователь не найден', 'error');
return;
}
$user = $users[0];
$saved_otp = get_saved_otp($user->ID);
if(!$saved_otp || $saved_otp != $code) {
wc_add_notice('Неверный код подтверждения или срок действия истек', 'error');
return;
}
// Авторизация пользователя
wp_set_auth_cookie($user->ID);
wp_redirect(wc_get_page_permalink('myaccount'));
exit;
}
}Проверка результата после внедрения
- Перейдите на страницу входа WooCommerce, проверьте, что форма запрашивает номер телефона и отправку OTP;
- Введите зарегистрированный номер, нажмите «Получить код», проверьте получение SMS;
- Введите полученный код, убедитесь, что происходит успешный вход и перенаправление на аккаунт;
- Попробуйте ввести неверный код — должна появиться ошибка;
- Проверьте, что код не действует после 5 минут (или заданного срока);
- Анализируйте логи ошибок отправки SMS;
- Тестируйте на разных устройствах и браузерах.
Частые ошибки и как их исправить
- Не приходит SMS: проверьте правильность API-ключей и номера отправителя в SMS-сервисе, убедитесь, что баланс аккаунта положительный;
- Пользователь не найден по номеру: убедитесь, что номера телефонов пользователей корректно и однородно сохранены (формат, без пробелов/скобок); можно добавить нормализацию номера перед поиском;
- Код OTP не сохраняется или не совпадает: проверьте работу transient, не очищается ли слишком быстро, убедитесь, что пользователь ID передаётся корректно;
- Форма не меняется: проверьте подключение скриптов, отсутствие конфликтов с темой или другими плагинами;
- Безопасность передачи данных: используйте HTTPS, убедитесь, что AJAX-запросы проходят с nonce и проверкой прав.
Практические советы по безопасности и производительности
- Ограничьте количество попыток отправки OTP за короткий период, чтобы предотвратить спам и атаки перебором;
- Используйте WP Nonce для AJAX-запросов, чтобы защититься от CSRF;
- Храните OTP в transient с небольшим сроком действия (3-5 минут);
- Используйте валидацию и нормализацию номера телефона (например, с библиотекой libphonenumber);
- Кэшируйте страницы WooCommerce аккуратно, исключая страницы входа и аккаунта;
- Для WooCommerce с большим трафиком рекомендуем использовать очереди для отправки SMS (например, через WP Cron или внешние сервисы);
- Логи ошибок отправки SMS сохраняйте в отдельный файл для быстрого мониторинга.
Сравнение способов реализации авторизации через SMS OTP
| Метод | Плюсы | Минусы | Пример |
|---|---|---|---|
| Плагин готовый (например, "OTP Login for WooCommerce") | Быстрая установка, минимум кода | Могут быть платными, ограниченные настройки, зависимость от стороннего кода | Clearfy Pro может помочь с оптимизацией, но OTP лучше отдельным плагином |
| Кастомный код с SMS API | Полный контроль, можно интегрировать с любыми сервисами, гибкая логика | Требует знаний, времени на разработку, поддержку | Пример из статьи |
| Двухфакторная авторизация на базе плагинов (Google Authenticator и т.п.) | Доп. уровень защиты | Сложнее для пользователей, не всегда удобно | Плагины типа Two-Factor |