Мультиустройственные сессии

Мультиустройственные сессии в контексте Iron-библиотеки в JavaScript строятся вокруг идеи безопасного хранения состояния пользователя без постоянного обращения к серверному хранилищу при каждом запросе. Основной принцип заключается в том, что каждое устройство рассматривается как независимый контекст авторизации, связанный с общей учетной записью пользователя через набор зашифрованных и валидируемых сессионных данных.

Ключевым элементом выступает модель, в которой сессия не является единым глобальным объектом. Вместо этого используется набор сессий, привязанных к устройствам, где каждая сессия содержит минимальный набор идентифицирующей информации и криптографически защищена.

Базовая модель мультиустройственного состояния

В Iron-подходе сессия обычно хранится в зашифрованной cookie-структуре. При переходе к мультиустройственной модели добавляется слой абстракции:

  • пользовательская сущность
  • список активных устройств
  • отдельные сессионные контексты для каждого устройства
  • метаданные безопасности (время, IP, fingerprint)

Структура данных на уровне логики может выглядеть следующим образом:

{
  userId: "u_10291",
  sessions: [
    {
      deviceId: "dev_1a3f",
      issuedAt: 1710000000,
      lastSeen: 1710003600,
      ip: "192.168.0.10",
      userAgent: "Chrome / Windows",
      refreshTokenId: "rt_abc123"
    },
    {
      deviceId: "dev_9bc2",
      issuedAt: 1710005000,
      lastSeen: 1710009000,
      ip: "10.0.0.5",
      userAgent: "Safari / iOS",
      refreshTokenId: "rt_def456"
    }
  ]
}

Iron здесь используется как механизм защиты этого состояния при хранении на клиентской стороне или при передаче через cookie.

Роль Iron в мультиустройственной модели

Библиотека Iron обеспечивает шифрование и подпись данных, которые передаются между клиентом и сервером. В мультиустройственном сценарии это особенно важно, поскольку:

  • каждое устройство получает собственный зашифрованный контекст
  • исключается возможность прямого изменения структуры сессии
  • обеспечивается целостность списка активных устройств

Основная идея заключается в том, что сервер не хранит состояние сессии в памяти для каждого запроса, а восстанавливает его из зашифрованного payload.

Идентификация устройства

Для корректной работы мультиустройственных сессий необходимо введение устойчивого идентификатора устройства. Он формируется либо:

  • на стороне клиента при первом входе
  • либо на сервере и закрепляется за refresh token

Часто используется комбинация:

  • user-agent
  • случайный UUID, сохранённый в localStorage
  • дополнительные сигналы (timezone, screen resolution)

Важно, что fingerprint не должен быть единственным фактором идентификации, так как он нестабилен.

Разделение access и refresh логики

В мультиустройственных сессиях используется разделение:

  • access token — краткоживущий токен для запросов
  • refresh token — долгоживущий идентификатор сессии устройства

Каждое устройство получает свой refresh token, связанный с записью в массиве активных сессий.

Сценарий обновления:

  1. устройство отправляет refresh token
  2. сервер проверяет его в зашифрованной структуре Iron
  3. создаётся новый access token
  4. при необходимости обновляется lastSeen

Обновление мультиустройственного состояния

При каждом действии пользователя происходит обновление метаданных устройства:

  • lastSeen обновляется
  • IP может меняться
  • фиксируются новые признаки активности

Это приводит к необходимости аккуратной сериализации состояния перед повторным шифрованием.

function updateDeviceSession(session, deviceId, patch) {
  return {
    ...session,
    sessions: session.sessions.map(s =>
      s.deviceId === deviceId
        ? { ...s, ...patch, lastSeen: Date.now() }
        : s
    )
  };
}

После обновления структура снова проходит через Iron encryption перед отправкой клиенту.

Принцип изоляции устройств

Каждое устройство должно быть изолировано логически, даже если принадлежит одному пользователю. Это означает:

  • токены не пересекаются
  • logout на одном устройстве не обязан влиять на другие
  • каждая сессия может иметь свой уровень доверия

Иногда вводится политика глобального logout, но она реализуется как отдельная операция, изменяющая весь массив sessions.

Выход с конкретного устройства

Удаление сессии устройства требует точечной модификации структуры:

function revokeDevice(session, deviceId) {
  return {
    ...session,
    sessions: session.sessions.filter(s => s.deviceId !== deviceId)
  };
}

После этого обновлённая структура повторно шифруется и отправляется клиенту или сохраняется на сервере.

Конкурентные сессии и гонки состояния

Мультиустройственная архитектура сталкивается с проблемой конкурентных обновлений:

  • два устройства могут одновременно обновлять сессию
  • возможна потеря изменений при перезаписи cookie

Для решения применяются:

  • versioning структуры сессии
  • timestamp-based conflict resolution
  • серверная авторитетная проверка refresh токенов

Пример версии:

{
  version: 42,
  sessions: [...]
}

Если приходит устаревшая версия, она отклоняется или пересобирается на сервере.

Безопасность в мультиустройственных сессиях

Основные угрозы:

  • кража cookie
  • replay атакa
  • подмена устройства
  • фиксация старого refresh token

Iron снижает риск модификации данных за счёт:

  • шифрования payload
  • HMAC-подписи
  • невозможности подделки структуры без ключа

Дополнительные меры:

  • привязка к IP диапазону (мягкая)
  • контроль аномальной географии входов
  • ограничение количества активных устройств

Ротация токенов

При каждом refresh рекомендуется обновлять refresh token:

  • старый помечается как недействительный
  • создаётся новый идентификатор
  • обновляется запись устройства

Это снижает риск повторного использования украденного токена.

Iron позволяет хранить сложные структуры в cookie, что особенно полезно для мультиустройственного сценария с ограниченной серверной инфраструктурой.

import { seal, unseal } from '@hapi/iron';

const sealed = await seal(sessionData, password, {
  encryption: 'aes256',
  integrity: 'sha256'
});

const unsealed = await unseal(sealed, password, {
  encryption: 'aes256',
  integrity: 'sha256'
});

В реальных системах данные обычно минимизируются перед шифрованием, чтобы не превышать лимиты cookie.

Масштабирование мультиустройственных сессий

При росте количества пользователей возникают ограничения:

  • размер cookie
  • нагрузка на сериализацию/десериализацию
  • частота обновлений сессий

Оптимизации включают:

  • хранение только deviceId и refreshTokenId в Iron
  • перенос детальных данных в серверное хранилище
  • кэширование активных сессий

Гибридная модель хранения

На практике применяется комбинированный подход:

  • Iron cookie хранит идентификатор сессии и минимальные метаданные
  • сервер хранит полную модель устройств
  • клиент получает только актуальный access token

Это снижает нагрузку на клиентскую сторону и повышает контроль безопасности.

Поведение при компрометации устройства

Если устройство скомпрометировано, возможны сценарии:

  • удаление одной сессии
  • инвалидирование всех сессий пользователя
  • принудительная ротация всех refresh tokens

Система должна уметь быстро перестраивать состояние:

function revokeAllSessions(session) {
  return {
    ...session,
    sessions: []
  };
}

Синхронизация состояния между устройствами

Мультиустройственные системы часто требуют синхронизации:

  • уведомления о новом входе
  • оповещения о выходе
  • обновление профиля в реальном времени

Реализуется через:

  • WebSocket каналы
  • server-sent events
  • периодический polling

Каждое изменение сессии может триггерить событие обновления состояния на всех активных устройствах.

Поведенческие ограничения и контроль доступа

Для повышения безопасности вводятся политики:

  • лимит количества устройств
  • запрет одновременного входа с разных регионов
  • блокировка подозрительных сессий

Эти правила применяются поверх базовой Iron-структуры и не входят в саму криптографическую модель, но тесно с ней взаимодействуют.