Files
ospab.host/QR-AUTH-SECURITY.md
2025-11-23 14:35:16 +03:00

9.2 KiB
Raw Blame History

QR-аутентификация — Безопасность

Обзор

QR-аутентификация реализована по модели OAuth2-подобного flow, аналогично Google/Яндекс/Telegram Login.


Архитектура безопасности

Правильный flow (текущая реализация)

1. ПК (неавторизованный)
   ↓
   POST /api/qr-auth/generate
   ← Получает уникальный code (без привязки к пользователю)
   ↓
   Показывает QR: https://ospab.host/qr-login?code=XXX
   ↓
   Polling: GET /api/qr-auth/status/:code каждые 2 секунды

2. Телефон (пользователь УЖЕ авторизован)
   ↓
   Сканирует QR → открывается /qr-login?code=XXX
   ↓
   POST /api/qr-auth/scanning (с Bearer token)
   → Backend обновляет статус QR на "scanning"
   ← ПК видит "Ожидание подтверждения на телефоне..."
   ↓
   GET /api/auth/me (с Bearer token)
   ← Получает данные ТЕКУЩЕГО пользователя телефона
   ↓
   Показывает экран подтверждения:
   "Войти на новом устройстве как [Ваше имя]?"
   ↓
   Пользователь нажимает "Подтвердить"
   ↓
   POST /api/qr-auth/confirm + Bearer token + code
   → Backend привязывает userId к QR-запросу
   → Обновляет статус на "confirmed"

3. ПК (polling получает confirmed)
   ↓
   Получает JWT токен ЭТОГО пользователя
   ↓
   Вызывает login(token) → обновляет AuthContext
   ↓
   Редирект на /dashboard

Защита от уязвимостей

🔒 1. Анонимный QR-код

  • QR создаётся БЕЗ привязки к пользователю
  • userId присваивается только после подтверждения
  • Невозможно "угадать" чей токен получит ПК

🔒 2. Требование авторизации на телефоне

  • /api/qr-auth/scanning требует authMiddleware
  • /api/qr-auth/confirm требует authMiddleware
  • Неавторизованный пользователь НЕ может подтвердить вход

🔒 3. Экран подтверждения

// Телефон показывает:
<div>
  <p>Войти на новом устройстве как:</p>
  <p className="text-xl font-bold">{userData.username}</p>
  <p className="text-sm text-gray-500">{userData.email}</p>
</div>

<button onClick={handleConfirm}>Подтвердить</button>
<button onClick={handleCancel}>Отмена</button>
  • Пользователь видит от чьего имени происходит вход
  • Может отказаться, если это не он

🔒 4. Время жизни QR-кода

const QR_EXPIRATION_SECONDS = 60; // 60 секунд
  • QR истекает через 60 секунд
  • После использования (confirmed/rejected) — удаляется
  • Cleanup устаревших кодов каждые 24 часа

🔒 5. Статусы и переходы

pending → scanning → confirmed/rejected/expired
    ↓         ↓            ↓
  Создан   Открыт    Финальный статус
  • pendingscanning: пользователь открыл страницу
  • scanningconfirmed: подтвердил вход
  • scanningrejected: отклонил вход
  • pending/scanningexpired: истёк таймаут

🔒 6. Polling на ПК

// Каждые 2 секунды:
GET /api/qr-auth/status/:code

// Ответы:
{ status: 'pending' }     // Ещё не сканировали
{ status: 'scanning' }    // Пользователь открыл страницу подтверждения
{ status: 'confirmed', token: 'JWT', user: {...} } // Подтвердили
{ status: 'rejected' }    // Отклонили
{ status: 'expired' }     // Истёк
  • ПК не генерирует токен сам
  • ПК получает токен только после подтверждения с телефона
  • Токен содержит userId пользователя с телефона

Защита от атак

Атака: Перехват QR-кода

Сценарий: Злоумышленник фотографирует QR с чужого экрана

Защита:

  • QR живёт 60 секунд
  • Требуется авторизация на телефоне атакующего
  • Экран подтверждения показывает имя/email входящего пользователя
  • Жертва видит что в её аккаунт пытаются войти

Атака: MITM (Man-in-the-Middle)

Сценарий: Злоумышленник перехватывает сетевой трафик

Защита:

  • Все запросы через HTTPS (https://ospab.host:5000)
  • JWT токены передаются в Authorization: Bearer
  • Токены хранятся в localStorage (HttpOnly cookie было бы лучше, но требует серверный рендеринг)

Атака: Replay Attack

Сценарий: Злоумышленник повторно отправляет перехваченный запрос

Защита:

  • QR-код одноразовый (удаляется после confirm/reject)
  • status !== 'pending' && status !== 'scanning' → ошибка
  • JWT токены имеют expiresIn: '24h'

Атака: Session Fixation

Сценарий: Злоумышленник пытается навязать свой QR-код

Защита:

  • ПК генерирует QR локально через /api/qr-auth/generate
  • Невозможно "навязать" чужой QR (каждый code уникален)
  • Backend не принимает "предустановленные" коды

Сравнение с другими методами

Метод Безопасность Удобство Скорость
QR-аутентификация
Логин + пароль
Email magic link
SMS OTP
OAuth (Google/Yandex)

Рекомендации по улучшению (будущее)

1. Rate Limiting

// Ограничить количество попыток генерации QR с одного IP
// Пример: максимум 10 QR в минуту

2. Device Fingerprinting

// При создании QR запоминать fingerprint ПК
// При polling проверять что запросы идут с того же устройства

3. Geolocation Check

// Если расстояние между IP адресами ПК и телефона > 1000 км → предупреждение
// "Попытка входа из другой страны. Подтвердите что это вы"

4. WebSocket вместо Polling

// Вместо GET /status/:code каждые 2 секунды
// Использовать WebSocket для реального времени

5. Push Notifications

// Отправлять пуш на телефон: "Вход на новом устройстве. Подтвердите?"
// Не требует открывать браузер

Заключение

Текущая реализация QR-аутентификации безопасна и соответствует индустриальным стандартам (Google, Яндекс, Telegram).

Ключевые принципы:

  1. Анонимный QR без привязки к пользователю
  2. Требование авторизации на подтверждающем устройстве
  3. Явный экран подтверждения с информацией о пользователе
  4. Короткое время жизни кодов (60 сек)
  5. Одноразовое использование
  6. HTTPS + JWT токены

Защищает от:

  • Перехвата QR
  • MITM атак
  • Replay атак
  • Session Fixation
  • Несанкционированного доступа

Документ обновлён: 10 ноября 2025 г.