Токен сессии, записанный в SharedPreferences, обычный файл или журнал, может попасть в резервную копию, crash-report или на устройство с расширенным доступом. Полностью защитить секрет на скомпрометированном телефоне нельзя, но можно заметно сократить срок и последствия утечки.

Access token делают короткоживущим и по возможности держат в памяти. Refresh token сохраняют через платформенное защищенное хранилище Keychain/Keystore с подходящими параметрами, ротируют на сервере и отзывают при выходе или подозрительной активности.

Что проверить в первую очередь

Сначала зафиксируйте точный сценарий, время ошибки и последнее известное рабочее состояние. Не меняйте несколько настроек одновременно: один контролируемый шаг должен подтверждать или исключать одну гипотезу. Перед работой с данными и конфигурацией подготовьте резервную копию и понятный способ отката.

  • Найдите все места записи токена: SharedPreferences, SQLite, файлы, state, логи и crash analytics.
  • Разделите access token, refresh token, device id и несекретные настройки.
  • Проверьте срок жизни, ротацию и серверный механизм отзыва.
  • Уточните настройки backup и accessibility защищенного хранилища на iOS и Android.

Почему возникает проблема

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

  • Библиотека сохраняет токен как обычную строку в preferences.
  • HTTP-interceptor или debug-лог печатает Authorization header.
  • Access и refresh token имеют одинаково долгий срок и не ротируются.
  • При выходе очищается интерфейс, но токен остается в storage и push-привязке.
  • Ключ защищенного хранилища доступен после восстановления backup на другом устройстве.

Пошаговая диагностика

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

  • Выполните поиск по имени ключа и фрагменту тестового токена в файлах sandbox и логах.
  • Проверьте release-сборку: debug и production могут использовать разные логгеры.
  • Проследите login, refresh, logout, удаление аккаунта и смену пароля.
  • Проверьте поведение после перезагрузки, блокировки экрана и переноса резервной копии.
  • На сервере найдите активные refresh-сессии и убедитесь, что их можно отозвать по устройству.

Как разделить access и refresh token

Короткий access token снижает окно злоупотребления, а refresh token требует более строгого хранения и серверного контроля.

  • Access token можно держать в памяти и получать заново после перезапуска через refresh.
  • Refresh token хранится в platform secure storage и привязан к серверной сессии устройства.
  • При каждом обновлении refresh token ротируется, а повтор старого значения считается сигналом риска.
  • Сервер хранит хеш или идентификатор сессии, срок, устройство и время последнего использования.
  • Чувствительные операции могут требовать повторной аутентификации независимо от активной сессии.

Как исправить проблему

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

  • Перенесите refresh token в flutter_secure_storage с проверенными платформенными опциями.
  • Уберите токены из SharedPreferences, URL, аналитики, логов и текста исключений.
  • Сократите срок access token и внедрите ротацию refresh token на backend.
  • При logout, смене пароля и удалении аккаунта отзывайте серверную сессию и очищайте storage.
  • Добавьте обработку недоступности/сброса Keychain или Keystore без бесконечного цикла входа.

Безопасный порядок внедрения

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

Как проверить результат

Разовый успешный тест недостаточен. Повторите операцию, проверьте крайние значения, параллельные действия и восстановление после перезапуска или временного сбоя. Для важного сценария сохраните автоматический тест либо короткий регрессионный чек-лист.

  • Тестовый токен отсутствует в preferences, файлах, логах и crash-отчете.
  • Выход немедленно делает refresh token недействительным на сервере.
  • Повтор старого refresh token после ротации отклоняется и фиксируется.
  • Переустановка, восстановление backup и смена устройства не открывают чужую сессию.

Типичные ошибки при исправлении

  • Шифровать токен ключом, жестко записанным в коде приложения.
  • Считать secure storage абсолютной защитой на root/jailbreak устройстве.
  • Логировать полный HTTP request в production.
  • Удалять только локальный токен без серверного отзыва сессии.

Как предотвратить повторение

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

  • Добавьте статическую проверку логов и секретов перед release.
  • Поддерживайте список активных устройств и возможность завершить каждую сессию.
  • Мониторьте reuse refresh token и необычную смену контекста.
  • Регулярно обновляйте библиотеку secure storage и проверяйте изменения платформенных настроек.

Что подготовить для технического разбора

  • Описание ожидаемого и фактического поведения, а также точную последовательность действий.
  • Время проблемы, идентификатор тестового объекта и версии затронутых компонентов.
  • Фрагменты журналов до и после ошибки без секретов и персональных данных.
  • Перечень последних изменений и уже выполненных проверок.
  • Безопасный доступ к тестовой среде или способ воспроизвести сбой без влияния на клиентов.

Частые вопросы

Можно ли хранить access token в secure storage?

Можно, но короткоживущий access token часто достаточно держать в памяти. Главное — не превращать его в долгоживущий секрет и не записывать в логи.

Защищает ли certificate pinning украденный токен?

Нет. Pinning защищает часть сетевого канала, но не делает уже украденный bearer token бесполезным. Нужны короткий срок, ротация и отзыв.

Когда нужна помощь специалиста

Если Flutter-приложение хранит токены в preferences или не умеет отзывать сессии, я могу переработать клиентское хранение и серверную ротацию, проверить logout и исключить утечки через логи и резервные копии.