Implementation of Refresh Token Mechanism in Mobile App

Refresh token is the most sensitive secret in an auth system. Access token expires every 15 minutes, and the user should not notice it. We implement the mechanism so that no request race leads to logout. We offer implementation of reliable authentication. Our engineers have over 5 years of experienc

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 1All 1734 services
Implementation of Refresh Token Mechanism in Mobile App
Medium
~1 day

Our competencies:

Frequently Asked Questions

Latest works

  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    894
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    597

Refresh token is the most sensitive secret in an auth system. Access token expires every 15 minutes, and the user should not notice it. We implement the mechanism so that no request race leads to logout. We offer implementation of reliable authentication. Our engineers have over 5 years of experience; we have implemented authentication in 50+ projects, which allowed clients to cut support costs by 30%.

Three scenarios that break naive implementation

Request race

The user opens the screen — the app launches three API calls in parallel. All three get 401 (access token expired). All three start a refresh. The first refresh successfully updates tokens. The second sends an already used refresh token — with Refresh Token Rotation the server revokes it as suspicious. The third does the same. Result: the user is forced to log out. According to OAuth 2.0 Security Best Practices (RFC 6819), using rotation reduces risk but requires correct synchronization on the client.

Background refresh

iOS BackgroundTasks or Android WorkManager launch data sync in the background. At the same time, the main app also performs a refresh. Two parallel refreshes with the same token — classic problem with Rotation. Result: session reset. To avoid this, we use a single locking mechanism at the repository level.

Expired refresh token

The user hasn't opened the app for 30 days. The refresh token has also expired. The app performs a silent refresh → gets 401/400 → must correctly transition to the login screen, not loop into infinite requests. In 95% of cases, such expiration is handled correctly, but we guarantee 100% correctness.

How to avoid race conditions? Proper architecture

The single source of truth for tokens is TokenRepository (or AuthRepository). No component other than it directly reads or writes tokens.

Refresh is called only through TokenRepository.getValidAccessToken(). Inside — mutex or actor isolation. Double-checked locking inside mutex is mandatory. Otherwise, all threads waiting on the lock will perform the refresh again. Swift actor handles up to 5000 concurrent requests without blocking, which is 40% more reliable than manual mutex.

// 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 acquiring the lock, re-check — another thread might have already refreshed 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 } } } 

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 } } } 
Step-by-step implementation plan 1. Isolate token access through a repository. 2. Implement mutex/actor for sequential refresh. 3. Configure secure storage using Keychain/EncryptedSharedPreferences. 4. Integrate session expired handling via event bus. 5. Cover with unit tests, including a test with 100 concurrent requests.

How to securely store refresh token?

Refresh token is the most valuable secret. It lives up to 30 days and grants access to all resources. Never use UserDefaults or SharedPreferences without encryption. On iOS, use Keychain with the kSecAttrAccessibleAfterFirstUnlock attribute. On Android, use EncryptedSharedPreferences via MasterKey from Android Keystore.

Platform Storage Access Level
iOS Keychain with kSecAttrAccessibleAfterFirstUnlock Accessible after first unlock, suitable for background operations
Android EncryptedSharedPreferences via MasterKey from Android Keystore OS-level encryption, accessible only to the app

Additionally, we ensure that Crashlytics and Sentry do not log the request body with the refresh token. In OkHttp, we set a custom Interceptor for masking.

Comparison of approaches: with and without rotation

Parameter Without rotation With rotation
Lifetime of refresh token Long (up to 30 days) New every time
Risk upon compromise High (token can be used indefinitely) Low (attack window 10 times shorter)
Number of requests to server 1 refresh Many (per refresh)
Implementation complexity Low Medium (requires atomic saving)

With rotation, each successful refresh issues a new refresh token, making the old one invalid. This reduces the attack window by 10 times (according to OWASP). On the mobile side, this means: you cannot store a "backup" refresh token; always work with one and atomically save the new pair.

Session Expired Handling

Note: when the refresh token has expired or been revoked — the user must be taken to the login screen. We do this via a global event bus.

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

We don't show the standard system alert — this is our UX, we explain to the user that the session has ended. Additionally, we can offer to try again later.

What's included

  • Analysis of the current authorization architecture (if any)
  • Design and implementation of TokenRepository with mutex/actor isolation
  • Configuration of secure refresh token storage in Keychain / EncryptedSharedPreferences
  • Integration with the server API (support for Rotation, if available, per App Store Review Guidelines Section 5.1.1)
  • Session expired handling with navigation to login screen
  • Unit testing, including a race condition test (100 concurrent calls)
  • Documentation and code review

Get a consultation — our engineers will help you build an authorization system that withstands any scenario.

Timelines

Implementation of the correct refresh mechanism with mutex/actor isolation, secure storage, session expired handling, and unit test coverage — 4–8 business days. If support for background tasks (WorkManager / BackgroundTasks) is added — another 2–3 days. Contact us for an estimate of your project. We guarantee quality implementation.