Implementing Session Management in Mobile App
Session in mobile app — concept broader than authorization token. This is complex of states: credential relevance, user activity, device change behavior, security event response (password change on server, session revocation by admin).
What full session management includes
Most projects stop at "have token — user authorized". Reality requires accounting for:
- Inactivity timeout. App locks after N minutes without interaction. For fintech — 3–5 minutes, corporate — 15–30 minutes, consumer — usually not needed.
- Force logout on password change. Backend revokes all active refresh tokens on password change. App must correctly handle 401 on refresh as SessionExpired, not network error.
- Parallel sessions. How many devices can be logged in simultaneously? If one — server revokes previous session on new login, app gets 401 and must explain what happened.
- Session recovery after app restart. Cold start — check token validity in Keychain/Keystore before showing any content.
Inactivity timeout: implementation
iOS: UIApplication.shared.sendAction doesn't fit for app-wide interaction tracking. Correct way — subclass UIWindow and override sendEvent(_:):
class ActivityTrackingWindow: UIWindow {
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
if event.type == .touches {
SessionManager.shared.resetInactivityTimer()
}
}
}
SessionManager holds Timer, which after N minutes publishes sessionInactivityTimeout event. Navigation coordinators react — show lock screen (biometrics or PIN).
Android: Handler + Runnable with postDelayed. Reset on every MotionEvent in base Activity:
abstract class BaseActivity : AppCompatActivity() {
private val inactivityHandler = Handler(Looper.getMainLooper())
private val lockRunnable = Runnable { SessionManager.onInactivity() }
override fun onUserInteraction() {
super.onUserInteraction()
inactivityHandler.removeCallbacks(lockRunnable)
inactivityHandler.postDelayed(lockRunnable, INACTIVITY_TIMEOUT_MS)
}
}
Important: pause timer on background (onPause) and resume on foreground (onResume). When app in background — other mechanism (absolute time background start).
Background timeout
Track time in background separately. On onPause/sceneDidEnterBackground save Date.now(). On onResume/sceneWillEnterForeground calculate delta. If exceeds threshold — show lock screen without animation (immediately, before user sees content).
// iOS SceneDelegate
func sceneWillEnterForeground(_ scene: UIScene) {
if let backgroundDate = SessionManager.shared.backgroundDate,
Date().timeIntervalSince(backgroundDate) > SessionConfig.backgroundTimeout {
SessionManager.shared.lockSession()
}
}
Multi-device and revocation
Server must have endpoint for getting active session list and revoking them. Mobile app — UI for this list: "Sessions" screen with devices, last activity date, "Terminate this session" button.
On session revocation from another device: next API request returns 401. If this 401 on refresh attempt — SessionExpired. Distinguish important: "401 because access token expired" (do refresh) vs "401 because refresh token revoked" (SessionExpired). Difference: on access token expiration, refresh returns 200, on revoked refresh token — 400/401 with error code invalid_grant.
Session states
Convenient to model as sealed class/enum:
sealed class SessionState {
object Active : SessionState()
object Locked : SessionState() // need biometrics/PIN
object Expired : SessionState() // need re-login
object Loading : SessionState() // checking tokens on startup
}
Global StateFlow<SessionState> in SessionManager — all app parts react to state change. Navigation coordinator/AppCoordinator subscribes and switches root view controller/NavHost depending on state.
Timeframe
Full session management (inactivity timeout, background timeout, lock screen, multi-device, SessionExpired flow) — 8–12 business days. If only basic part needed (timeout + lock) — 4–6 days. Server part (session storage, revocation API) — separate estimate with backend team.







