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 и expiration:
{
"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) |
| Scope | Определённый набор прав | Полный объём прав пользователя |
| Отзыв | Через короткий 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 данной сессии и блокирует доступ. Легитимный пользователь перенаправляется на страницу входа. Это предотвращает атаки с кражей 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также