Реалізація авторизації через 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 днів.







