Refresh Token for Mobile Apps — Essence, Refresh Mechanism, and Secure Storage

Author: IT Sectr Published: 2026-04-05 Reading time: 9 min

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 — a long-lived token for obtaining a new access token without re-login
  • Short-lived access token — reduces risk in case of a leak: an attacker gains access for 15–30 minutes
  • Token rotation — each refresh request returns a new refresh token, the old one is invalidated
  • Secure storage — iOS Keychain, Android EncryptedSharedPreferences, never in NSUserDefaults
  • Refresh token reuse detection — protection against theft: if a stolen refresh token is used, the session is blocked

What Is a Refresh Token?

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.

How Does a Refresh Token Work

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.

Token Refresh Flow

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:

json
{
  "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.

Refresh Token vs Access Token

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.

ParameterAccess TokenRefresh Token
Lifetime15–60 minutesDays, weeks, or months
Transmission frequencyEvery API requestOnly during refresh
Client storageMemory / short-termSecure (Keychain / EncryptedSharedPrefs)
ScopeSpecific set of permissionsFull user permissions
RevocationThrough short TTLServer blacklist / deletion
FormatJWT or opaqueUsually opaque (random string)

Why an Access Token Cannot Be Long-Lived

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.

Refresh Token Security

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.

Storing Refresh Tokens on Mobile Devices

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

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.

Reuse Detection

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.

Kotlin Implementation

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.

kotlin
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

How is a refresh token different from an access token?

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.

How often should the access token be refreshed?

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.

Can a refresh token be revoked on the server?

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.

What happens if two clients use the old refresh token simultaneously?

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.

Where to securely store a refresh token on iOS?

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

  • Refresh Token — a long-lived token for refreshing an access token without re-login
  • Short TTL for access token (15–60 min) minimizes damage from a leak
  • Token rotation — each refresh returns a new refresh token, the old one is invalidated
  • Reuse detection — detects token theft and blocks the session
  • Storage — iOS Keychain, Android EncryptedSharedPreferences (AES256-GCM)
  • Server revocation — removing the refresh token from the DB on logout or password change
  • Refresh token is never transmitted with regular API requests

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.

Discuss the project

Read also