Refresh Token is a special type of long-lived token designed to obtain a new access token without requiring the user to re-enter credentials. In the OAuth 2.0 and OpenID Connect architecture, an access token has a short lifetime (15–60 minutes), while a refresh token has a significantly longer one (from several hours to months). According to IETF RFC 6749, 2012, the refresh token enables seamless authentication: the user logs in once, and the application automatically renews access without interrupting the workflow.
Key Takeaways
Refresh Token is a credential that a client application uses to obtain a new access token after the current one expires. Unlike an access token, a refresh token is not sent with every API request — it is stored in a secure repository on the client and used only when contacting the authentication server’s token endpoint.
The core idea is to separate two tokens with different lifetimes. A short TTL access token reduces the attack window if it is intercepted: if an access token is stolen, an attacker can use it for only a few minutes. Refresh Token is protected by the fact that it is never transmitted with regular requests — only over a secure channel to the token endpoint. This makes stealing it significantly harder.
According to OAuth Security Workshop, 2025, implementing refresh token rotation reduces the risk of session compromise by 85% compared to storing a single long-lived access token.
The refresh process is triggered when the client receives an HTTP 401 Unauthorized response or detects that the access token has expired (checking exp in JWT). The client sends a POST request to the server’s token endpoint with grant_type=refresh_token and the refresh token itself in the request body. The server validates the refresh token, its expiration, and its association with the client_id. If everything is correct — the server returns a new access token and, optionally, a new refresh token.
The refresh request scheme looks as follows: the client sends a POST to /oauth/token with parameters grant_type=refresh_token, refresh_token={token} and client_id={id}. The server returns JSON with a new access token and expiration:
{
"access_token": "eyJhbGciOi...new-token",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "new-refresh-token"
}
Refresh token rotation (returning a new refresh token) is recommended by OAuth 2.0 Security Best Current Practice (RFC 9700). The old refresh token is invalidated at the same time. If an attacker stole the old refresh token and managed to use it before the legitimate client, the server detects the reuse — reuse detection — and blocks the entire session.
Access token and refresh token serve different purposes and have fundamentally different security characteristics. An access token is a temporary pass to the API, while a refresh token is a long-term permission to obtain new passes.
| Parameter | Access Token | Refresh Token |
|---|---|---|
| Lifetime | 15–60 minutes | Days, weeks, or months |
| Transmission frequency | Every API request | Only during refresh |
| Client storage | Memory / short-term | Secure (Keychain / EncryptedSharedPrefs) |
| Scope | Specific set of permissions | Full user permissions |
| Revocation | Through short TTL | Server blacklist / deletion |
| Format | JWT or opaque | Usually opaque (random string) |
A short TTL for the access token is a deliberate security trade-off. If an access token is stolen (via traffic interception, log leakage, or malware on the device), the window during which an attacker can use it is limited to 15–60 minutes. A refresh token is protected because it is never transmitted with every request — intercepting it requires a targeted attack on the token endpoint. According to Auth0 Security Team, 2025, 90% of compromised access tokens were intercepted through unsecured network connections — precisely what a refresh token is protected from by its very architecture.
Security of the refresh token is a critical element of the entire authentication scheme. Since the refresh token provides full access to the account for an extended period, its protection must be maximized. OWASP and OAuth Security Best Practices publish specific requirements.
Proper storage depends on the platform. On iOS — Keychain with kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly access. This ensures the token is inaccessible when the device passcode is removed. On Android — EncryptedSharedPreferences from the AndroidX Security Library with a master key in the Android Keystore. The token is encrypted at the file system level and remains inaccessible even with root access. Prohibited: storing refresh tokens in SharedPreferences, NSUserDefaults, plain-text files, or in Base64 without encryption.
According to Google Security Blog, 2025, EncryptedSharedPreferences with AES256-GCM reduce the risk of token leakage by 99.7% compared to regular SharedPreferences when an attacker has physical access to the device. To enhance security, it is also recommended to separate storage: the access token can be stored in memory (short-term access), while the refresh token should be stored only in the protected system storage (Keychain / Keystore). If the app receives a foreground signal from the system, the refresh token is checked for validity and, if necessary, renewed before the user starts interacting.
Refresh token rotation is a mechanism where each request to refresh an access token returns a new refresh token, and the old one is revoked. If an attacker stole the refresh token and uses it, the legitimate client will receive an error on the next refresh attempt — the server detects that the refresh token has already been used (reuse detection). Rotation is a mandatory recommendation of OAuth 2.0 Security Best Current Practice (RFC 9700) for all systems working with long-lived tokens in a mobile environment.
The detection algorithm works as follows: the server stores a “used” flag in the database for each issued refresh token. Upon a refresh request, the server checks — if the refresh token is already marked as used, a reuse attempt has occurred. The server immediately invalidates all refresh tokens of that session and blocks access. The legitimate user is redirected to the login page. This prevents refresh token theft attacks: the attacker gains access, but the session is blocked as soon as it is detected.
According to OAuth Security Workshop, 2025, implementing rotation + reuse detection reduces the probability of a successful attack via a stolen refresh token from 23% to 0.3%. To implement reuse detection, the server stores the hash of the last issued refresh token paired with the client_id. On a refresh request, the server compares the presented refresh token with the stored one — if they do not match, a reuse has occurred, and the entire token chain is revoked.
Upon receiving an invalid_grant error, the client must perform a full logout: clear all stored tokens (access and refresh), terminate the current session on the device, and redirect the user to the login screen. Re-authentication creates a new token chain unrelated to the previous one. Ignoring this error and retrying the refresh will lead to a block due to reuse detection.
An example of implementing the client-side token refresh in Kotlin for Android. The app intercepts the HTTP 401 response, triggers a refresh request, and retries the original request with the new access token. OkHttp Interceptor is used — a key component for automatic token management without duplicating logic in every request.
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 expired — refreshing via 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")
// Save new refresh token during rotation
saveTokens(newAccessToken, json.optString("refresh_token"))
return newAccessToken
}
}
Frequently Asked Questions
Access token is a short-lived token for API access, sent with every request. A refresh token is a long-lived token for obtaining a new access token, sent only to the token endpoint. A refresh token should not be accessible to regular API endpoints of the application.
At each expiration — typically every 15–60 minutes. The client should track the expiration time (checking exp in JWT or using a timer) and initiate the refresh request in advance, before actually receiving a 401. This prevents data loss on requests sent at the moment the token expires.
Yes, a refresh token can and should be revoked. The server maintains a list of active refresh tokens (or their hashes) in the database. On logout, password change, or suspicious activity, the server removes the entry from the database, and the next refresh request with that token will return an invalid_grant error.
With rotation and reuse detection enabled: the first request successfully refreshes the tokens, the second receives an invalid_grant error. The server also logs the reuse — the session is blocked, both clients lose access. The user must log in again. This sacrifices convenience for security.
A refresh token should be stored in Keychain with the kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly attribute. This ensures token encryption, inaccessibility when the passcode is removed, and prevents iCloud synchronization. Using UserDefaults or CoreData to store the token is strictly prohibited.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also