Реализация авторизации через JWT-токены в мобильном приложении
Недавний кейс: клиент хранил JWT в SharedPreferences — после восстановления из бэкапа злоумышленник получил доступ к API. Мы перевели хранение в Android Keystore, и инциденты прекратились. По данным OWASP ^1, неправильное хранение токенов входит в топ-10 уязвимостей мобильных приложений. Внедрение Keychain снижает риск утечки на 90% по сравнению с UserDefaults.
Мы внедряем JWT-авторизацию более 5 лет и реализовали её в 20+ проектах на iOS, Android и гибридных платформах. Наш подход исключает утечки через хранилище и race condition при обновлении токенов. Вот как это делается.
Почему UserDefaults — плохой выбор для JWT?
UserDefaults (iOS) и SharedPreferences (Android) — plaintext хранилища. На iOS резервная копия в iTunes/iCloud включает эти данные. Через idevicebackup2 и специализированные утилиты JWT извлекается за минуты. В 90% случаев утечки токенов происходят именно из-за такого хранения. Keychain в 100 раз безопаснее благодаря аппаратному шифрованию — он защищает данные даже при физическом доступе к устройству.
| Хранилище | Безопасность | Резервное копирование | Рекомендация |
|---|---|---|---|
| UserDefaults / SharedPreferences | Низкая | Включается | Не использовать |
| Keychain (iOS) | Высокая (аппаратное шифрование) | Только с разрешения пользователя | Использовать |
| EncryptedSharedPreferences (Android) | Средняя (ключ в Keystore) | Зависит от реализации | Использовать |
| AsyncStorage (RN) | Низкая | Включается | Использовать с react-native-keychain |
Что проверять на клиенте?
Мобильное приложение может декодировать JWT и читать claims — для отображения имени, проверки ролей, определения времени истечения. Верифицировать подпись на клиенте бессмысленно при HS256, так как секретный ключ не должен быть известен клиенту. Обязательные проверки:
-
exp— не истёк ли токен (с запасом ~30 секунд для clock skew) -
iss— ожидаемый издатель -
aud— токен предназначен для нашего приложения
При асимметричных алгоритмах RS256/ES256 клиент может верифицировать подпись через публичный ключ — это полезно для офлайн-сценариев. Библиотеки: JWTDecode.swift (iOS), java-jwt от Auth0 (Android), jwt-decode (React Native).
Как организовать автоматическое обновление токенов?
Access token живёт 15–60 минут, refresh token — дни/недели. Автоматическое обновление — задача HTTP-клиента. На iOS с URLSession используйте кастомный URLSessionTaskDelegate или middleware-паттерн; в Alamofire — RequestInterceptor. На Android с Retrofit — Authenticator (вызывается при 401) или Interceptor (проверяет exp до запроса).
// Android Retrofit Authenticator class TokenAuthenticator(private val tokenRepo: TokenRepository) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { if (response.code != 401) return null val newToken = runBlocking { tokenRepo.refreshToken() } ?: return null return response.request.newBuilder() .header("Authorization", "Bearer $newToken") .build() } } Важна защита от параллельных refresh-запросов. Если пять запросов одновременно получили 401 — пять попыток refresh. Правильно: Mutex (Kotlin) / NSLock (Swift) или async let с actor (Swift Concurrency). Первый поток делает refresh, остальные ждут результата.
// iOS — actor для serialized refresh actor TokenRefreshActor { private var refreshTask: Task<String, Error>? func refreshIfNeeded(using service: AuthService) async throws -> String { if let task = refreshTask { return try await task.value } let task = Task { try await service.refresh() } refreshTask = task defer { refreshTask = nil } return try await task.value } } Как обработать logout и revoke?
JWT stateless по природе — нельзя «отозвать» токен без дополнительной инфраструктуры. Короткий exp + refresh token rotation — основная защита. При logout:
- Удаляем токены из Keychain/Keystore.
- Вызываем
/auth/logoutна сервере — сервер инвалидирует refresh token в базе. - Если сервер ведёт blocklist — access token тоже инвалидируется немедленно.
Пункт 2 и 3 — серверная работа. Мобильная сторона обязана вызвать logout endpoint, даже если пользователь в оффлайне (ставим в очередь через WorkManager / BackgroundTasks).
Сравнение алгоритмов HS256 vs RS256
| Свойство | HS256 | RS256 |
|---|---|---|
| Тип ключа | Симметричный (один секрет) | Асимметричный (публичный/приватный) |
| Клиентская верификация | Невозможна (ключ не раскрывается) | Возможна через публичный ключ |
| Производительность | Высокая | Ниже (~2x медленнее) |
| Рекомендация | Для внутренних микросервисов | Для мобильных клиентов |
Что входит в работу
Мы предоставляем:
- Безопасное хранение токенов (Keychain/Keystore)
- Интерцептор для автоматического refresh с защитой от race condition
- Интеграцию logout с инвалидацией на сервере
- Unit-тесты ключевых сценариев
- Документацию для команды
- Передачу доступа к хранилищу и подписанные сборки
- Обучение команды заказчика
- Поддержку в течение месяца после внедрения
Процесс работы над JWT-авторизацией
- Анализ существующей схемы и токенов
- Проектирование безопасного хранения (Keychain/Keystore)
- Реализация интерцептора для автоматического refresh (с защитой от race condition)
- Интеграция logout с инвалидацией на сервере
- Unit-тесты ключевых сценариев
- Документация для команды
- Передача доступа к хранилищу и подписанные сборки
Сроки: JWT auth с нуля — 4–7 рабочих дней. Если нужна интеграция с существующим бэкендом — плюс 2–3 дня на синхронизацию. Заказчики, внедрившие нашу схему, экономят в среднем 40% бюджета на исправление уязвимостей.
Мы гарантируем, что ваши токены не окажутся в открытом виде, а пользовательский опыт останется плавным. Свяжитесь с нами для аудита вашей системы авторизации — найдём уязвимости и предложим решения. Закажите внедрение безопасной JWT-схемы — получите результат за 4-7 дней.







