Refresh Token Mechanism for Mobile App

NOVASOLUTIONS.TECHNOLOGY is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Refresh Token Mechanism for Mobile App
Medium
~1 business day
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Implementing Refresh Token Mechanism in Mobile App

Refresh token — most underestimated part of auth system. Access token expires — user shouldn't notice. In practice, half of "app logs out user" problems come from incorrect refresh mechanism.

Three scenarios breaking naive implementation

Request race. User opens screen — app launches three API calls in parallel. All three get 401 (access token expired). All three start refresh. First refresh successfully updates tokens. Second sends already used refresh token — with Refresh Token Rotation server revokes it as suspicious. Third same. Result: user force logged out even though access token really just expired.

Background refresh. iOS BackgroundTasks or Android WorkManager starts data sync in background. At same time main app also does refresh. Two parallel refresh with one token — classic problem with Rotation.

Expired refresh token. User didn't open app 30 days. Refresh token also expired. App does silent refresh → gets 401/400 → should correctly go to login screen, not loop on endless requests.

Correct architecture

Only source of truth for tokens — TokenRepository (or AuthRepository). No other component reads/writes tokens directly.

Refresh called only via TokenRepository.getValidAccessToken(). Inside — mutex or actor isolation:

// Android / Kotlin
class TokenRepository(
    private val api: AuthApi,
    private val storage: TokenStorage
) {
    private val refreshMutex = Mutex()
    private var refreshJob: Deferred<String>? = null

    suspend fun getValidAccessToken(): String {
        val current = storage.getAccessToken()
        if (current != null && !current.isExpired()) return current

        return refreshMutex.withLock {
            // After getting lock re-check — another thread might have refreshed already
            val refreshed = storage.getAccessToken()
            if (refreshed != null && !refreshed.isExpired()) return@withLock refreshed

            val newTokens = api.refresh(storage.getRefreshToken()
                ?: throw SessionExpiredException())
            storage.saveTokens(newTokens)
            newTokens.accessToken
        }
    }
}

Double-checked locking inside mutex — mandatory. Otherwise all threads waiting lock do refresh again.

On iOS with Swift Concurrency — actor:

actor TokenStore {
    private var isRefreshing = false
    private var waiters: [CheckedContinuation<String, Error>] = []

    func getValidToken(refresher: AuthService) async throws -> String {
        let stored = storage.accessToken
        if let token = stored, !token.isExpired { return token.value }

        if isRefreshing {
            return try await withCheckedThrowingContinuation { waiters.append($0) }
        }

        isRefreshing = true
        do {
            let tokens = try await refresher.refresh(storage.refreshToken)
            storage.save(tokens)
            waiters.forEach { $0.resume(returning: tokens.accessToken) }
            waiters.removeAll()
            isRefreshing = false
            return tokens.accessToken
        } catch {
            waiters.forEach { $0.resume(throwing: error) }
            waiters.removeAll()
            isRefreshing = false
            throw error
        }
    }
}

Refresh Token storage

Refresh token — most sensitive secret. Lives longer, gives more rights (get new access token).

  • iOS: Keychain with kSecAttrAccessibleAfterFirstUnlock (accessible after first unlock, including background ops) or kSecAttrAccessibleWhenUnlocked (only when unlocked, if background not needed).
  • Android: EncryptedSharedPreferences via MasterKey from Android Keystore.

Never log refresh token. Check that Crashlytics, Sentry and other SDKs don't capture HTTP requests with refresh token in body. In OkHttp — custom Interceptor with masking sensitive headers/body before passing to crashlytics.

Refresh Token Rotation

If server supports Rotation: each successful refresh returns new refresh token, old invalidated. This limits attack window on token compromise.

Consequence for mobile: cannot save "second" refresh token as backup. Always work with one, atomically save new pair after refresh.

SessionExpired handling

When refresh token expired or revoked — must tell user and move to login screen. Do this via global event bus or Notification/Flow:

// Kotlin / Coroutines
object AuthEvents : MutableSharedFlow<AuthEvent>() // in singleton
// In TokenRepository on 401 on refresh:
AuthEvents.emit(AuthEvent.SessionExpired)
// In Activity/Fragment:
lifecycleScope.launch {
    AuthEvents.collect { if (it == AuthEvent.SessionExpired) navigateToLogin() }
}

Don't show standard system alert — this is our UX, explain to user that session finished.

Timeframe

Implementing correct refresh mechanism with mutex/actor isolation, correct storage, SessionExpired handling, and unit test coverage (including request race test) — 4–8 business days. If adding background task support (WorkManager/BackgroundTasks) — another 2–3 days.