← Назад к вопросам

Как спроектировать двухфакторную авторизацию?

2.0 Middle🔥 181 комментариев
#JavaScript Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Проектирование двухфакторной аутентификации (2FA)

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

Ключевые принципы и выбор факторов

  1. Независимость факторов: Второй фактор должен быть из категории, отличной от «знание» (пароль). Чаще всего используется:
    *   **Владение (Possession):** OTP-токен (TOTP/HOTP), SMS-код, Push-уведомление, аппаратный ключ (YubiKey).
    *   **Биометрия (Inherence):** Отпечаток пальца, Face ID (чаще на стороне клиента).

  1. Баланс безопасности и удобства (UX): Навязывание 2FA для всех действий убивает UX. Стратегия:
    *   **Обязательная 2FA** для критических операций (смена пароля, платежи, доступ к настройкам).
    *   **Сессионная 2FA:** Включить один раз при входе с нового устройства/браузера, затем использовать **долгоживущий токен сессии**.
    *   **Доверенные устройства:** Позволить пользователю отмечать устройства, где 2FA не требуется какое-то время.

Архитектура бэкенда

Бэкенд должен иметь отдельный, чётко выделенный слой логики для 2FA.

// Пример структуры модуля (Node.js + Express)
class TwoFactorService {
    async generateSecret(userId) {
        // Генерируем секрет для TOTP (библиотека: speakeasy/otplib)
        const secret = speakeasy.generateSecret({
            name: `MyApp:${userEmail}`,
        });
        // Сохраняем encryptedSecret в БД, привязанный к userId
        await userModel.save2FASecret(userId, encrypt(secret.base32));
        return secret; // Для отображения QR-кода
    }

    async verifyCode(userId, userProvidedToken) {
        // Достаём секрет пользователя из БД
        const userSecret = await userModel.get2FASecret(userId);
        // Валидируем OTP-код
        const verified = speakeasy.totp.verify({
            secret: decrypt(userSecret),
            encoding: 'base32',
            token: userProvidedToken,
            window: 1 // Допускаем небольшую рассинхронизацию по времени
        });
        return verified;
    }

    async sendSMSorPush(userId) {
        // Логика отправки кода через SMS (Twilio) или Push (Firebase)
        // Генерация и сохранение одноразового кода с коротким TTL (Time-To-Live)
    }
}

Поток данных и этапы реализации

  1. Настройка (Enrollment):
    *   Пользователь в настройках безопасности включает 2FA.
    *   Бэкенд генерирует **секрет (seed)**, сохраняет его (в зашифрованном виде) в профиле пользователя и возвращает в виде URI для QR-кода (формат `otpauth://`).
    *   Фронтенд отображает QR-код (используя библиотеку, например `qrcode`), который пользователь сканирует в приложении-аутентификаторе (Google Authenticator, Authy).
    *   Для подтверждения пользователь вводит первый код из приложения, который проверяется бэкендом. Только после успеха статус 2FA активируется.

  1. Процесс аутентификации:
    *   Пользователь вводит логин и пароль (Фактор 1).
    *   Если пароль верен И у пользователя активирована 2FA, бэкенд отправляет на фронтенд признак необходимости второго фактора (например, `{ requires2FA: true }`).
    *   Фронтенд перенаправляет на страницу ввода кода 2FA или показывает соответствующий модальный диалог.
    *   Пользователь вводит 6-значный код из аутентификатора или код из SMS.
    *   Фронтенд отправляет код на специальный endpoint (например, `POST /api/auth/verify-2fa`).
    *   Бэкенд проверяет код. При успехе:
        *   Генерирует полную **сессионную пару (access + refresh token)**.
        * **Никогда не использует код 2FA как часть токена** — это лишь условие для выдачи токена.
        *   Фиксирует в аудит-логе успешный вход с 2FA.

Критически важные меры безопасности на фронтенде и бэкенде

  • Резервные коды (Backup Codes): При активации 2FA генерировать и безопасно показывать пользователю 10 одноразовых кодов для входа на случай потери телефона. Хранить их только в хэшированном виде.
  • Rate Limiting: Жёстко ограничивать попытки верификации кода 2FA (например, 5 попыток с одного IP) для предотвращения брутфорса.
  • Аудит и оповещения: Логировать все события: включение/отключение 2FA, неудачные попытки верификации. Отправлять email-оповещение при любом изменении настроек 2FA.
  • Безопасная деактивация: Предусмотреть процесс восстановления доступа через коды восстановления (backup codes) или доверенный контакт (email резервный) с задержкой (например, 24-48 часов) для противодействия взлому аккаунта злоумышленником.
  • Защита на фронтенде:
    // Не кэшируем страницы, связанные с 2FA
    <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate">
    // Используем Captcha (например, hCaptcha) на форме ввода кода после нескольких ошибок
    // Маскируем введённый код (например, заменяем цифры на • после ввода)
    

Выбор технологии

  • TOTP (Time-based One-Time Password) — «золотой стандарт». Использует общий секрет и текущее время. Надёжен, работает офлайн. Библиотеки: speakeasy (Node.js), pyotp (Python), google-auth (Java).
  • Push-уведомления — UX-премиум. Пользователь просто нажимает «Подтвердить» в мобильном приложении. Требует отдельного сервиса и инфраструктуры.
  • SMS — ненадёжно (риск SIM-свопа, задержки), но просто для пользователя. Использовать только как опцию, но не как основную.
  • WebAuthn/FIDO2 — будущее. Использование биометрии или аппаратных ключей (пасс-ключей). Наиболее безопасно, но требует поддержки браузера и более сложной реализации.

Итоговый дизайн должен быть модульным: ядро — TOTP, с возможностью добавления альтернативных методов (SMS, Push) через абстракцию. Главное — дать пользователю выбор и чётко объяснять риски каждого метода, сохраняя баланс между безопасностью и удобством.