MBL
Go / Проекты / SmartParking / ParkTrack — авторизация и истечение сессии
Go средний

ParkTrack — авторизация и истечение сессии

projectsmobileflutter

Архитектурная схема (archify) →

Самое интересное здесь — не сама авторизация (обычный Bearer-токен), а то, как истечение сессии одной строкой кода долетает до навигации и переключает весь экран без единого ручного вызова Navigator.push.

Токен на каждый запрос

AuthInterceptor.onRequest читает токен из TokenStorage (защищённое хранилище, flutter_secure_storage) и подставляет Authorization: Bearer $token в заголовки — на уровне Dio-интерцептора, а не в каждом отдельном API-вызове вручную. Ни один экран или репозиторий не думает про токен явно.

Цепочка от 401 до экрана логина

Четыре независимых слоя связаны в одну реакцию, каждый ничего не знает о соседях сверх необходимого:

  1. AuthInterceptor.onError — если сервер вернул 401, интерцептор чистит токены (_tokenStorage.clearTokens()) и ставит sessionExpiredNotifier.value = true.
  2. sessionExpiredNotifier — это простой ValueNotifier<bool> из flutter/foundation, а не Riverpod-провайдер. Он специально живёт вне Riverpod: AuthInterceptor — обычный Dart-класс без доступа к Ref, и именно ValueNotifier даёт дешёвый способ "просигналить наружу" из кода, который в дереве провайдеров не участвует.
  3. AuthNotifier (Riverpod StateNotifier) подписывается на этот ValueNotifier в конструкторе (addListener) и в колбэке _onSessionExpired переводит СВОЁ состояние в AuthState.unauthenticated(). Так сигнал из "внешнего" мира попадает обратно в Riverpod-граф.
  4. routerProvider делает ref.watch(authStateProvider) — как только состояние стало unauthenticated, срабатывает redirect, и go_router перекидывает пользователя на /login с ЛЮБОГО экрана, где бы он ни был в этот момент (карта, поиск, профиль).
AuthInterceptor (401) → ValueNotifier → AuthNotifier.state → routerProvider.redirect → /login

Почему это устроено именно так, а не через прямой Navigator

Если бы AuthInterceptor дёргал Navigator напрямую, пришлось бы протаскивать BuildContext или глобальный NavigatorKey в сетевой слой — смешение ответственности (HTTP-код внутри интерцептора решал бы, КАК именно рисуется навигация). Вместо этого интерцептор просто меняет один bool, а решение "что теперь показать" полностью остаётся на go_router, который и так уже отвечает за то, какой экран показывать при каком состоянии авторизации — единая точка принятия решения о маршруте, а не две (обычный логин/логаут и отдельно "экстренный" выход по 401).

Проверка себя: checkSession() в AuthNotifier защищена флагами _isCheckingSession/_sessionChecked, чтобы повторный вызов (например, при пересборке виджета) не гонял /auth/me заново — вызывается ровно один раз за жизнь провайдера, с таймаутом 8 секунд и явным unauthenticated при любой ошибке или таймауте, а не зависанием в loading.

Самопроверка 0 / 2
Могу перечислить всю цепочку от HTTP 401 до редиректа на экран логина
Понимаю, зачем нужен ValueNotifier как мост между Dio (вне Riverpod) и Riverpod-состоянием
Как усвоено?