ModTwoAuth: двухфакторная аутентификация для MODX 3 - TOTP, Email, Telegram и Passkeys
Есть решение
Я решил открыть доступ к ModTwoAuth, компонент двухфакторной аутентификации для MODX 3. Он добавляет второй шаг при входе в админку и в личный кабинет на сайте: код из приложения-аутентификатора, код на email, код или кнопка в Telegram, а также WebAuthn — Touch ID, Face ID, Windows Hello и аппаратные ключи. Админка и фронтенд включаются независимо друг от друга.

Работает с Google Authenticator, Authy, Aegis и любым приложением по RFC 6238. На вкладке «🔐 2FA» в профиле пользователь жмёт «Сгенерировать QR», сканирует его телефоном и вводит текущий 6-значный код. После трёх неверных попыток секрет аннулируется — нужно запрашивать новый QR.
Секрет 160-битный, в базу попадает уже зашифрованным. Допускается расхождение часов на ±1 шаг (30 секунд), а принятый код запоминается по timestep и повторно не проходит: перехваченный или подсмотренный код через минуту уже бесполезен.

Одноразовый 6-значный код уходит на подтверждённый адрес через почту MODX, достаточно настроенного SMTP. Код живёт modtwoauth_code_lifetime секунд (по умолчанию 300), между повторными отправками выдерживается пауза modtwoauth_resend_cooldown (60 секунд), чтобы кнопку «Отправить ещё раз» нельзя было жамкать бесконечно.
Тема и HTML-текст письма лежат в словарях modtwoauth_email_subject и modtwoauth_email_body, в них работают плейсхолдеры
Самый удобный метод для у кого сервер не глушит ТГ, кто не хочет ставить приложение-аутентификатор. Вы создаёте бота как обычно в @BotFather и вписываете токен в modtwoauth_telegram_bot_token, остальное пакет делает сам: вебхук регистрируется автоматически при создании запроса на привязку или вход, секрет вебхука выводится из токена бота и проверяется на каждом запросе.
Пользователь сканирует QR или переходит по deep-link, нажимает Start, затем «Подтвердить подключение» в боте. Запрос одноразовый и живёт 5 минут, в базе хранятся только хеши токенов. При входе бот присылает 6-значный код и кнопки «Разрешить вход» / «Отклонить» — подтвердить может только тот чат, который привязан к аккаунту.
КАРТИНКА
Вход по отпечатку пальца, лицу или аппаратному ключу — вообще без кодов. Поддерживаются ES256, RS256 и Ed25519, residentKey и userVerification в режиме preferred, так что passkeys работают без заранее заведённого списка credentials. Ключей можно зарегистрировать несколько: телефон, ноутбук, YubiKey.
RP ID компонент определяет по хосту — manager_url для админки и site_url для фронтенда. Для нестандартных поддоменов задайте modtwoauth_webauthn_rp_id, туда можно вписать как example.com, так и полный URL. Счётчик подписей аутентификатора проверяется, поэтому склонированный ключ не пройдёт; у аутентификаторов без счётчика проверка пропускается. Тип аттестации — none, цепочка сертификатов вендора не проверяется, это осознанное решение.

В Системные настройки → ModTwoAuth включите modtwoauth_enable_2fa.
Каждый менеджер настраивает метод себе сам: Пользователи → своя учётная запись → вкладка «🔐 2FA» — QR, код из письма, привязка Telegram или регистрация Passkey, затем переключатель «Включить 2FA».
Со следующего входа MODX запросит код после пароля.
Важно: 2FA действует только для пользователей, которые настроили и включили метод. Включённая системная настройка сама по себе никого не заблокирует — можно спокойно включать компонент на работающем сайте и дать менеджерам время на настройку.
Страница подтверждения берётся из чанка modtwoauthVerifyTpl, её адрес задаётся настройкой modtwoauth_verify_url (по умолчанию /2fa). Если дружественные URL выключены, работает прямой адрес /assets/components/modtwoauth/verify.php.
&methods можно оставить только нужные методы (totp,email,telegram,webauthn), если оставить пустым покажутся все.
&tpl ID чанка-шаблона (по умолчанию `modtwoauthFrontendSettingsTpl`).

&showCurrent скрыть текущую сессию из списка (по умолчанию 1).
&tpl ID чанка-шаблона (по умолчанию `modtwoauthSessionsTpl`).
список активных сессий

Оба работают только для авторизованных пользователей и в любом веб-контексте.
Сессии — то, ради чего я и создала пакет, так в TwoFactorX мне много чего не хватало. В списке видны IP, браузер и время последней активности, токены сессий в интерфейс не выводятся.
Чужую сессию можно отозвать одной кнопкой: запись удаляется, и при следующем запросе она завершится. Текущую сессию отозвать нельзя. Отключение 2FA требует ввода текущего пароля.
Страницу подтверждения на фронтенде можно оформить в своем стиле -укажите ID чанка в modtwoauth_verify_chunk_id. При пустом или неверном значении компонент показывает к стандартный шаблону, на админку эта настройка не влияет.

Второй фактор передаётся только через штатные страницы подтверждения. Обойти его запросом с внешним флагом нельзя — таких флагов нет.
При блокировке вход отклоняется, а не «проваливается» в проверку пароля.
Коды из писем и Telegram хранятся только как bcrypt-хеши.
Все формы подтверждения защищены CSRF-токенами, коннектор и процессоры требуют POST и сверяют Origin/Referer, список действий коннектора-allowlist.
При включённой 2FA нельзя «тихо» перезаписать метод прямым вызовом процессора — сначала отключение через профиль.
ID сессии регенерируется после успешного второго фактора во всех потоках: менеджер, фронтенд, Telegram, WebAuthn.
Редиректы после 2FA проверяются по схеме, хосту и порту сайта, open redirect не получится.
Сообщения сервера экранируются перед вставкой в DOM, детали исключений пишутся только в журнал MODX.
Ключ можно вынести за пределы сайта — настройкой modtwoauth_key_path или переменной окружения MODTWOAUTH_KEY_PATH.
Если TOTP уже работает, не генерируйте новый ключ: сначала перенесите существующий файл, и только потом меняйте настройку.
PHP 8.1+ с расширением sodium (входит в PHP по умолчанию с 7.2).
HTTPS — обязателен для Telegram-вебхука и WebAuthn.
ЧПУ для url /2fa и /2fa-web; без них работают прямые адреса.
Скачать: github.com/vitos88/ModTwoAuth/releases/tag/v1.0.0
Modx: extras.modx.com/package/modtwoauth
Modstore: добавлю когда восстановится
0

Что внутри
- Четыре метода второго фактора:
- TOTP (RFC 6238)
- Telegram
- WebAuthn / FIDO2 / Passkeys.
Отдельные настройки для менеджера и для пользователей сайта, фронтенд совместим со сниппетом Login. - Защита от перебора: 3 неверных кода блокируют вход на 15 минут. Состояние блокировки хранится в базе и переживает перезапуск PHP-процессов, тот же лимит действует при первоначальной настройке метода.
- TOTP-секреты лежат в базе только зашифрованными (libsodium XChaCha20-Poly1305), ключ шифрования — вне директории компонента.
- Список активных сессий с отзывом по одной и принудительным завершением сессий старше заданного возраста.
- QR-коды создаются локально в браузере встроенной библиотекой: одноразовые токены не уходят на за пределы.
- Диагностика безопасности прямо на главной странице компонента: ключ, sodium, HTTPS, флаги cookie, CSP, статус плагинов.
TOTP: код из приложения
Работает с Google Authenticator, Authy, Aegis и любым приложением по RFC 6238. На вкладке «🔐 2FA» в профиле пользователь жмёт «Сгенерировать QR», сканирует его телефоном и вводит текущий 6-значный код. После трёх неверных попыток секрет аннулируется — нужно запрашивать новый QR.
Секрет 160-битный, в базу попадает уже зашифрованным. Допускается расхождение часов на ±1 шаг (30 секунд), а принятый код запоминается по timestep и повторно не проходит: перехваченный или подсмотренный код через минуту уже бесполезен.

Email: код на почту
Одноразовый 6-значный код уходит на подтверждённый адрес через почту MODX, достаточно настроенного SMTP. Код живёт modtwoauth_code_lifetime секунд (по умолчанию 300), между повторными отправками выдерживается пауза modtwoauth_resend_cooldown (60 секунд), чтобы кнопку «Отправить ещё раз» нельзя было жамкать бесконечно.
Тема и HTML-текст письма лежат в словарях modtwoauth_email_subject и modtwoauth_email_body, в них работают плейсхолдеры
[[+code]]и[[+minutes]]Письмо можно переписать под свою вёрстку и перевести — словари в пакете есть на нескольких языках.Telegram: код или одна кнопка
Самый удобный метод для у кого сервер не глушит ТГ, кто не хочет ставить приложение-аутентификатор. Вы создаёте бота как обычно в @BotFather и вписываете токен в modtwoauth_telegram_bot_token, остальное пакет делает сам: вебхук регистрируется автоматически при создании запроса на привязку или вход, секрет вебхука выводится из токена бота и проверяется на каждом запросе.
Пользователь сканирует QR или переходит по deep-link, нажимает Start, затем «Подтвердить подключение» в боте. Запрос одноразовый и живёт 5 минут, в базе хранятся только хеши токенов. При входе бот присылает 6-значный код и кнопки «Разрешить вход» / «Отклонить» — подтвердить может только тот чат, который привязан к аккаунту.
КАРТИНКА
В Telegram одно жёсткое требование: сайт должен быть доступен из интернета по HTTPS. На localhost вебхук не зарегистрировать, поэтому метод удобнее проверять на боевом или тестовом сервере.
WebAuthn и Passkeys
Вход по отпечатку пальца, лицу или аппаратному ключу — вообще без кодов. Поддерживаются ES256, RS256 и Ed25519, residentKey и userVerification в режиме preferred, так что passkeys работают без заранее заведённого списка credentials. Ключей можно зарегистрировать несколько: телефон, ноутбук, YubiKey.
RP ID компонент определяет по хосту — manager_url для админки и site_url для фронтенда. Для нестандартных поддоменов задайте modtwoauth_webauthn_rp_id, туда можно вписать как example.com, так и полный URL. Счётчик подписей аутентификатора проверяется, поэтому склонированный ключ не пройдёт; у аутентификаторов без счётчика проверка пропускается. Тип аттестации — none, цепочка сертификатов вендора не проверяется, это осознанное решение.
WebAuthn тоже требует HTTPS, исключение — localhost.

Как включить в админке
Установите пакет через Приложения → Управление пакетами. Установщик создаст таблицы, сгенерирует ключ шифрования core/config/modtwoauth.key с правами 0600 и зашифрует TOTP-секреты, если они уже были сохранены открытым текстом.В Системные настройки → ModTwoAuth включите modtwoauth_enable_2fa.
Каждый менеджер настраивает метод себе сам: Пользователи → своя учётная запись → вкладка «🔐 2FA» — QR, код из письма, привязка Telegram или регистрация Passkey, затем переключатель «Включить 2FA».
Со следующего входа MODX запросит код после пароля.
Важно: 2FA действует только для пользователей, которые настроили и включили метод. Включённая системная настройка сама по себе никого не заблокирует — можно спокойно включать компонент на работающем сайте и дать менеджерам время на настройку.
Страница подтверждения берётся из чанка modtwoauthVerifyTpl, её адрес задаётся настройкой modtwoauth_verify_url (по умолчанию /2fa). Если дружественные URL выключены, работает прямой адрес /assets/components/modtwoauth/verify.php.
Фронтенд: личный кабинет на двух сниппетах
Для пользователей сайта есть отдельная настройка modtwoauth_frontend_enable_2fa (по умолчанию включёна). В личном кабинете достаточно вывести два сниппета:выбор и настройка метода
[[!ModTwoAuthFrontend]]доступны параметра&methods можно оставить только нужные методы (totp,email,telegram,webauthn), если оставить пустым покажутся все.
&tpl ID чанка-шаблона (по умолчанию `modtwoauthFrontendSettingsTpl`).

список активных сессий
[[!ModTwoAuthSessions]]доступны параметра&showCurrent скрыть текущую сессию из списка (по умолчанию 1).
&tpl ID чанка-шаблона (по умолчанию `modtwoauthSessionsTpl`).
список активных сессий

Оба работают только для авторизованных пользователей и в любом веб-контексте.
Сессии — то, ради чего я и создала пакет, так в TwoFactorX мне много чего не хватало. В списке видны IP, браузер и время последней активности, токены сессий в интерфейс не выводятся.
Чужую сессию можно отозвать одной кнопкой: запись удаляется, и при следующем запросе она завершится. Текущую сессию отозвать нельзя. Отключение 2FA требует ввода текущего пароля.
Страницу подтверждения на фронтенде можно оформить в своем стиле -укажите ID чанка в modtwoauth_verify_chunk_id. При пустом или неверном значении компонент показывает к стандартный шаблону, на админку эта настройка не влияет.

Безопасность
Отдельно про вещи, которые обычно всплывают при аудите:Второй фактор передаётся только через штатные страницы подтверждения. Обойти его запросом с внешним флагом нельзя — таких флагов нет.
При блокировке вход отклоняется, а не «проваливается» в проверку пароля.
Коды из писем и Telegram хранятся только как bcrypt-хеши.
Все формы подтверждения защищены CSRF-токенами, коннектор и процессоры требуют POST и сверяют Origin/Referer, список действий коннектора-allowlist.
При включённой 2FA нельзя «тихо» перезаписать метод прямым вызовом процессора — сначала отключение через профиль.
ID сессии регенерируется после успешного второго фактора во всех потоках: менеджер, фронтенд, Telegram, WebAuthn.
Редиректы после 2FA проверяются по схеме, хосту и порту сайта, open redirect не получится.
Сообщения сервера экранируются перед вставкой в DOM, детали исключений пишутся только в журнал MODX.
После установки пройдитесь по чек-листу:
HTTPS на менеджере и сайте, session.cookie_secure=1 и session.cookie_httponly=1 в PHP, отчёты CSP Report-Only просмотрены в консоли браузера до включения блокирующей политики. Все пункты продублированы в диагностике на главной странице компонента — там сразу видно, чего не хватает.
Ключ шифрования
Ключ создаётся установщиком в core/config/modtwoauth.key. Обязательно сделайте копию: без ключа зашифрованные TOTP-секреты не восстановить, пользователям придётся делать перевыпуск (секреты email, Telegram и WebAuthn при этом не страдают).Ключ можно вынести за пределы сайта — настройкой modtwoauth_key_path или переменной окружения MODTWOAUTH_KEY_PATH.
Если TOTP уже работает, не генерируйте новый ключ: сначала перенесите существующий файл, и только потом меняйте настройку.
Требования
MODX Revolution 3.x.PHP 8.1+ с расширением sodium (входит в PHP по умолчанию с 7.2).
HTTPS — обязателен для Telegram-вебхука и WebAuthn.
ЧПУ для url /2fa и /2fa-web; без них работают прямые адреса.
Где взять
GitHub: github.com/vitos88/ModTwoAuthСкачать: github.com/vitos88/ModTwoAuth/releases/tag/v1.0.0
Modx: extras.modx.com/package/modtwoauth
Modstore: добавлю когда восстановится
Комментарии: 0