The Decision: Reactive Refresh
For most React Native applications, a reactive refresh strategy is the superior choice. Relying on a 401 Unauthorized response to trigger the token exchange minimizes unnecessary network overhead and avoids the complexity of managing timers across app lifecycle states (foreground/background), which can lead to battery drain and memory leaks.
Trade-offs and Analysis
While proactive refresh (using expires_in) can theoretically eliminate the latency of a failed request, the trade-offs in a mobile environment are generally unfavorable:
- Battery & Network: Proactive timers trigger network requests regardless of whether the user is actively interacting with the app, wasting radio resources.
- Race Conditions: Proactive refresh can still suffer from race conditions if a token expires exactly as a request is sent.
- Complexity: Managing
setTimeout or background tasks in React Native to align with token expiry is fragile and often interrupted by the OS.
Coordinating Concurrent Requests
The primary risk of a reactive strategy is the "thundering herd" problem: multiple concurrent API calls failing simultaneously and each triggering a separate refresh request. To solve this, implement a Promise-based Singleton in your API interceptor:
- Create a module-level variable (e.g.,
let refreshTokenPromise = null;).
- When a 401 is intercepted, check if
refreshTokenPromise exists.
- If it exists, return that existing promise to the caller.
- If it does not, initiate the refresh call, assign the resulting promise to the variable, and clear the variable once the promise resolves or rejects.
This ensures that regardless of how many requests fail, only one exchange occurs with the authorization server.
Handling Refresh Token Expiration
When the refresh token itself is revoked or expired (e.g., the refresh endpoint returns a 400 or 401), the application must prioritize security over convenience. The safest UX path is:
- Purge: Immediately delete the refresh token from the iOS Keychain or Android Keystore.
- Clear: Wipe the in-memory access token.
- Redirect: Navigate the user to the login screen.
- Notify: Display a non-intrusive message (e.g., "Your session has expired. Please log in again.") to explain the sudden redirection.
Verification Steps
To verify this implementation, perform the following tests:
- Concurrency Test: Fire 5–10 simultaneous API requests with an expired access token. Verify in the network debugger that only one
/refresh call is dispatched.
- Revocation Test: Manually revoke the refresh token on the server and trigger an API call. Verify that the app clears secure storage and redirects to the login screen.
Note: This recommendation assumes the use of a secure storage wrapper (such as react-native-keychain) rather than AsyncStorage for the refresh token.