Refresh Token — це спеціальний тип довгоживучого токена, призначений для отримання нового access token без повторного введення облікових даних користувача. В архітектурі OAuth 2.0 та OpenID Connect access token має короткий термін життя (15–60 хвилин), а refresh token — значно довший (від кількох годин до місяців). За даними IETF RFC 6749, 2012, refresh token дозволяє реалізувати безшовну аутентифікацію: користувач входить один раз, а застосунок автоматично оновлює доступ, не перериваючи роботу.
Головне
Refresh Token — це облікові дані, які клієнтський застосунок використовує для отримання нового access token після закінчення терміну дії поточного. На відміну від access token, refresh token не надсилається з кожним API-запитом — він зберігається в безпечному сховищі на клієнті та використовується лише при зверненні до token endpoint сервера аутентифікації.
Основна ідея — розділення двох токенів різного терміну життя. Access token з коротким TTL знижує вікно атаки при його перехопленні: якщо access token вкрадено, зловмисник може використовувати його лише кілька хвилин. Refresh Token захищений тим, що ніколи не передається зі звичайними запитам — лише по захищеному каналу до token endpoint. Це робить його крадіжку значно складнішою.
За даними OAuth Security Workshop, 2025, впровадження refresh token rotation знижує ризик компрометації сесії на 85% порівняно зі зберіганням одного довгоживучого access token.
Процес оновлення запускається, коли клієнт отримує HTTP-відповідь 401 Unauthorized або виявляє, що access token закінчився (перевірка exp у JWT). Клієнт надсилає POST-запит на token endpoint сервера з grant_type=refresh_token і самим refresh token у тілі запиту. Сервер перевіряє валідність refresh token, його термін дії та належність до client_id. Якщо все коректно — сервер повертає новий access token та, опціонально, новий refresh token.
Схема запиту на оновлення виглядає наступним чином: клієнт надсилає POST на /oauth/token з параметрами grant_type=refresh_token, refresh_token={token} та client_id={id}. Сервер повертає JSON з новим access token та терміном дії:
{
"access_token": "eyJhbGciOi...новий-токен",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "новий-refresh-token"
}
Refresh token rotation (повернення нового refresh token) рекомендується OAuth 2.0 Security Best Current Practice (RFC 9700). Старий refresh token при цьому інвалідується. Якщо зловмисник вкрав старий refresh token і встиг використати його раніше легітимного клієнта, сервер виявить повторне використання — reuse detection — та заблокує всю сесію.
Access token та refresh token виконують різні функції та мають принципово різні характеристики безпеки. Access token — тимчасовий пропуск до API, refresh token — довгостроковий дозвіл на отримання нових пропусків.
| Параметр | Access Token | Refresh Token |
|---|---|---|
| Термін життя | 15–60 хвилин | Дні, тижні або місяці |
| Частота передачі | Кожний API-запит | Тільки при оновленні |
| Сховище на клієнті | Пам'ять / короткострокове | Безпечне (Keychain / EncryptedSharedPrefs) |
| Обсяг прав | Визначений набір прав | Повний обсяг прав користувача |
| Відкликання | Через короткий TTL | Серверний blacklist / видалення |
| Формат | JWT або opaque | Зазвичай opaque (випадковий рядок) |
Короткий TTL access token — це усвідомлений компроміс безпеки. Якщо access token вкрадено (через перехоплення трафіку, витік логів, шкідливе ПЗ на пристрої), час, протягом якого зловмисник може ним користуватися, обмежений 15–60 хвилинами. Refresh token захищений тим, що ніколи не передається по кожному запиту — його перехоплення вимагає цілеспрямованої атаки на token endpoint. За даними Auth0 Security Team, 2025, 90% скомпрометованих access token були перехоплені через незахищені мережеві з'єднання — те, від чого refresh token захищений самою архітектурою.
Безпека refresh token — критичний елемент всієї схеми аутентифікації. Оскільки refresh token надає повний доступ до облікового запису на тривалий термін, його захист має бути максимальним. OWASP та OAuth Security Best Practices публікують конкретні вимоги.
Правильне зберігання залежить від платформи. На iOS — Keychain з доступом kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. Це гарантує, що токен недоступний при знятому паролі пристрою. На Android — EncryptedSharedPreferences від AndroidX Security Library з майстер-ключем в Android Keystore. Токен шифрується на рівні файлової системи і недоступний навіть при root-доступі. Заборонено: зберігати refresh token у SharedPreferences, NSUserDefaults, plain-text файлах або в Base64 без шифрування.
За даними Google Security Blog, 2025, EncryptedSharedPreferences з AES256-GCM знижують ризик витоку токенів на 99.7% порівняно зі звичайними SharedPreferences при фізичному доступі до пристрою. Для посилення безпеки рекомендується також розділяти сховища: access token може зберігатися в оперативній пам'яті (короткостроковий доступ), refresh token — тільки в захищеному системному сховищі (Keychain / Keystore). Якщо застосунок отримує сигнал foreground від системи, refresh token перевіряється на валідність і при необхідності оновлюється до того, як користувач почне взаємодію.
Refresh token rotation — це механізм, при якому кожен запит на оновлення access token повертає новий refresh token, а старий анулюється. Якщо зловмисник вкрав refresh token та використовує його, легітимний клієнт при наступній спробі оновлення отримає помилку — сервер виявить, що refresh token вже був використаний (reuse detection). Rotation є обов'язковою рекомендацією OAuth 2.0 Security Best Current Practice (RFC 9700) для всіх систем, що працюють з довгоживучими токенами в мобільному середовищі.
Алгоритм detection працює так: сервер зберігає в базі ознаку "used" для кожного виданого refresh token. При запиті на оновлення сервер перевіряє — якщо refresh token вже позначений як використаний, значить, відбулася спроба повторного використання. Сервер негайно інвалідує всі refresh token даної сесії та блокує доступ. Легітимний користувач перенаправляється на сторінку входу.
За даними OAuth Security Workshop, 2025, при впровадженні rotation + reuse detection ймовірність успішної атаки через вкрадений refresh token знижується з 23% до 0.3%. Для реалізації reuse detection сервер зберігає хеш останнього виданого refresh token у парі з client_id. При запиті на оновлення сервер порівнює пред'явлений refresh token зі збереженим — якщо вони не збігаються, значить, відбулося повторне використання, і весь ланцюжок токенів анулюється.
При отриманні помилки invalid_grant клієнт повинен виконати повний logout: очистити всі збережені токени (access та refresh), завершити поточну сесію на пристрої та перенаправити користувача на екран входу. Повторна аутентифікація створює новий ланцюжок токенів, не пов'язаний з попереднім. Ігнорування цієї помилки та повторні спроби refresh призведуть до блокування за reuse detection.
Приклад реалізації клієнтської частини оновлення токена на Kotlin для Android. Застосунок перехоплює HTTP-відповідь 401, викликає refresh-запит і повторює вихідний запит з новим access token. Використовується OkHttp Interceptor — ключовий компонент для автоматичного управління токенами без дублювання логіки в кожному запиті.
class AuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val accessToken = getAccessToken()
val authRequest = request.newBuilder()
.addHeader("Authorization", "Bearer $accessToken")
.build()
val response = chain.proceed(authRequest)
if (response.code != 401) return response
// Термін дії access token закінчився — оновлюємо через refresh token
val newToken = refreshAccessToken() ?: return response
return chain.proceed(request.newBuilder()
.addHeader("Authorization", "Bearer $newToken")
.build())
}
private fun refreshAccessToken(): String? {
val refreshToken = getRefreshToken() ?: return null
val client = OkHttpClient()
val body = FormBody.Builder()
.add("grant_type", "refresh_token")
.add("refresh_token", refreshToken)
.build()
val request = Request.Builder()
.url("https://auth.example.com/oauth/token")
.post(body)
.build()
val response = client.newCall(request).execute()
val json = JSONObject(response.body?.string() ?: return null)
val newAccessToken = json.getString("access_token")
// Зберегти новий refresh token при rotation
saveTokens(newAccessToken, json.optString("refresh_token"))
return newAccessToken
}
}
Часті запитання
Access token — короткоживучий токен для доступу до API, передається з кожним запитом. Refresh token — довгоживучий токен для отримання нового access token, передається тільки на token endpoint. Refresh token не повинен бути доступний звичайним API-ендпоінтам застосунку.
При кожному закінченні терміну — зазвичай кожні 15–60 хвилин. Клієнт повинен відстежувати час закінчення (перевірка exp у JWT або таймер) та ініціювати refresh-запит заздалегідь, до фактичного отримання 401. Це запобігає втраті даних при запитах, відправлених у момент закінчення терміну токена.
Так, refresh token можна і потрібно відкликати. Сервер зберігає список активних refresh token (або їх хешів) в базі даних. При logout, зміні пароля або підозрілій активності сервер видаляє запис з БД, і наступний запит на оновлення з цим токеном поверне помилку invalid_grant.
При впровадженому rotation з reuse detection: перший запит успішно оновлює токени, другий отримує помилку invalid_grant. Сервер також фіксує повторне використання — сесія блокується, обидва клієнти втрачають доступ. Користувач повинен увійти заново. Це жертвує зручністю заради безпеки.
Зберігати refresh token потрібно в Keychain з атрибутом kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. Це гарантує шифрування токена, недоступність при знятому паролі та виключає синхронізацію через iCloud. Використовувати UserDefaults або CoreData для зберігання токена категорично заборонено.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також