Реализация сессионного управления в мобильном приложении
Сессия в мобильном приложении — понятие шире, чем токен авторизации. Это комплекс состояний: актуальность учётных данных, активность пользователя, поведение при смене устройства, реакция на события безопасности (смена пароля на сервере, отзыв сессии администратором).
Что включает полноценное session management
Большинство проектов останавливаются на "есть токен — пользователь авторизован". На деле нужно учитывать:
- Timeout по неактивности. Приложение блокируется после N минут без взаимодействия. Для финтех — 3–5 минут, для корпоративных — 15–30 минут, для потребительских — обычно не нужно.
- Принудительный logout при смене пароля. Бэкенд отзывает все активные refresh tokens при смене пароля. Приложение должно корректно обработать 401 на refresh как SessionExpired, а не как сетевую ошибку.
- Параллельные сессии. Сколько устройств может быть залогинено одновременно? Если одно — сервер отзывает предыдущую сессию при новом логине, приложение получает 401 и должно объяснить пользователю, что произошло.
- Восстановление сессии после перезапуска приложения. Cold start — проверяем валидность токенов в Keychain/Keystore до показа любого контента.
Timeout по неактивности: реализация
iOS: UIApplication.shared.sendAction не подходит для отслеживания взаимодействий на уровне всего приложения. Правильный способ — subclass UIWindow и переопределить sendEvent(_:):
class ActivityTrackingWindow: UIWindow {
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
if event.type == .touches {
SessionManager.shared.resetInactivityTimer()
}
}
}
SessionManager держит Timer, который по истечении N минут публикует событие sessionInactivityTimeout. На это событие реагируют координаторы навигации — показывают экран блокировки (биометрия или PIN).
Android: Handler + Runnable с postDelayed. Сбрасываем при каждом MotionEvent в базовом Activity:
abstract class BaseActivity : AppCompatActivity() {
private val inactivityHandler = Handler(Looper.getMainLooper())
private val lockRunnable = Runnable { SessionManager.onInactivity() }
override fun onUserInteraction() {
super.onUserInteraction()
inactivityHandler.removeCallbacks(lockRunnable)
inactivityHandler.postDelayed(lockRunnable, INACTIVITY_TIMEOUT_MS)
}
}
Важно: таймер паузируем при уходе в background (onPause) и возобновляем при возвращении (onResume). Когда приложение в background — другой механизм (абсолютное время background start).
Background timeout
Отдельно отслеживаем время в background. При onPause / sceneDidEnterBackground сохраняем Date.now(). При onResume / sceneWillEnterForeground вычисляем дельту. Если больше порога — показываем экран блокировки без анимации (сразу, до того как пользователь увидит контент).
// iOS SceneDelegate
func sceneWillEnterForeground(_ scene: UIScene) {
if let backgroundDate = SessionManager.shared.backgroundDate,
Date().timeIntervalSince(backgroundDate) > SessionConfig.backgroundTimeout {
SessionManager.shared.lockSession()
}
}
Мультиустройственность и revocation
Сервер должен иметь endpoint для получения списка активных сессий и их отзыва. Мобильное приложение — UI для этого списка: "Sessions" screen с устройствами, датой последней активности, кнопкой "Завершить эту сессию".
При отзыве сессии с другого устройства: следующий API-запрос вернёт 401. Если это 401 на попытке refresh — SessionExpired. Важно различать "401 потому что access token истёк" (делаем refresh) и "401 потому что refresh token отозван" (SessionExpired). Различие: при истечении access token refresh возвращает 200, при отозванном refresh token — 400/401 с кодом ошибки invalid_grant.
Состояния сессии
Удобно моделировать как sealed class / enum:
sealed class SessionState {
object Active : SessionState()
object Locked : SessionState() // нужна биометрия/PIN
object Expired : SessionState() // нужен повторный логин
object Loading : SessionState() // проверяем токены при запуске
}
Глобальный StateFlow<SessionState> в SessionManager — все части приложения реагируют на изменение состояния. Navigation coordinator / AppCoordinator подписывается и переключает root view controller / NavHost в зависимости от состояния.
Сроки
Полное session management (timeout по неактивности, background timeout, экран блокировки, мультиустройственность, SessionExpired flow) — 8–12 рабочих дней. Если нужна только базовая часть (timeout + блокировка) — 4–6 дней. Серверная часть (хранение сессий, revocation API) — отдельная оценка с backend-командой.







