Генерируйте коды с настоящей энтропией
Используйте криптографически стойкий генератор случайных чисел, никогда — предсказуемое зерно или значение, выведенное из метки времени. Шесть цифр — обычный баланс безопасности и удобства; более короткие коды требуют строже ограничивать частоту, чтобы оставаться безопасными.
Никогда не отправляйте один и тот же код дважды и никогда не делайте код угадываемым из id пользователя или последовательности. Вся безопасность SMS-OTP держится на непредсказуемости кода в его короткий срок жизни.
- Используйте CSPRNG (например, библиотеки secrets/crypto), а не PRNG общего назначения.
- 6 цифр — стандарт; если используете 4, жёстко ужесточите лимиты и срок действия.
- Один код на запрос; аннулируйте предыдущие коды при выпуске нового.
Храните коды безопасно, никогда — в открытом виде
Относитесь к OTP как к недолговечному паролю. Храните хеш кода (вместе с идентификатором и сроком действия), а не сырое значение, чтобы утечка базы не раскрыла живые коды. Сравнивайте функцией постоянного времени, чтобы избежать атак по времени, и удаляйте запись, как только код использован или истёк.
- Хешируйте код перед хранением; сравнивайте за постоянное время.
- Привязывайте каждый код к одному пользователю/сессии и назначению.
- Удаляйте при успехе, истечении или достижении лимита попыток.
Задайте короткое окно срока действия
Коды должны жить минуты, а не часы. Срок 5–10 минут — типичный диапазон: достаточно долго, чтобы задержавшаяся SMS ещё дошла, и достаточно коротко, чтобы утёкший код обычно уже был мёртв. Показывайте пользователю оставшийся срок, чтобы медленное сообщение не читалось как сломанный поток.
Сочетайте короткий срок действия с задержкой повторной отправки, иначе пользователи будут долбить «отправить снова» при задержке сообщения и умножат ваши расходы на SMS.
Ограничивайте частоту по каждому измерению
Ограничение частоты — то, что останавливает и перебор кодов, и злоупотребление вашей конечной точкой отправки (что стоит вам денег и может привести к пометке отправителя). Ограничивайте по номеру телефона, по IP и по аккаунту, и на отправке, и на проверке.
- Попытки проверки: ограничьте попытки на код (например, 5), затем аннулируйте и заставьте отправить заново.
- Запросы отправки: задержка между отправками и суточный лимит на номер.
- Глобальные ограждения: лимиты по IP и по аккаунту, чтобы притупить автоматизированное злоупотребление.
- Добавьте экспоненциальную задержку или CAPTCHA при многократном достижении лимитов.
Обрабатывайте повторы и запасные каналы
SMS работает по принципу best-effort, поэтому хороший поток исходит из того, что часть сообщений не дойдёт. Предложите повтор после задержки, а после одной-двух неудачных попыток SMS эскалируйте на запасной канал — голосовой звонок, зачитывающий код, WhatsApp или e-mail — именно это автоматизируют управляемые Verify-API.
Сделайте состояния сбоя человечными: понятный путь «не пришло?», возможность сменить номер и контакт поддержки после повторных сбоев.
- 1
Первая попытка: SMS
Отправьте код и запустите видимый обратный отсчёт с задержкой повторной отправки.
- 2
Повтор по запросу
Разрешите повтор после задержки; аннулируйте предыдущий код при выпуске нового.
- 3
Эскалируйте канал
После повторного сбоя SMS предложите голос или альтернативный канал для той же верификации.
- 4
Предложите выход
Дайте пользователю исправить номер или обратиться в поддержку вместо тупика.
Улучшайте UX без ослабления безопасности
Мелочи повышают долю завершений: используйте WebOTP API и форматы автозаполнения SMS, чтобы мобильные браузеры могли прочитать код, держите формулировку сообщения одинаковой (некоторые платформы кешируют доверенных отправителей) и никогда не включайте в сообщение ничего, кроме кода и названия приложения. Избегайте ссылок в OTP-сообщениях — они приучают пользователей кликать и запускают спам-фильтры операторов.
- Поддерживайте WebOTP / автозаполнение одноразового кода на мобильных.
- Держите сообщение минимальным: название приложения + код, без маркетинга, без ссылок.
- Локализуйте текст сообщения, но держите формат кода стабильным.