Skip to content

Authentication & Token Lifecycle

The mobile app authenticates with the backend using a JWT access/refresh token pair issued by Django SimpleJWT. This document covers token lifetimes, the proactive renewal strategy, and how the app handles failures to avoid spurious logouts.


Token Lifetimes

Token Lifetime Notes
Access token 30 minutes Used in the Authorization: Bearer header on every API request
Refresh token 7 days Used once to obtain a new access/refresh pair (rotation)

Why 30 minutes for the access token? Users frequently switch away from the app for several minutes — e.g. to watch onboarding videos, use a messaging app, or check another tool. A shorter lifetime (e.g. 15 minutes) combined with a 7-minute proactive renewal buffer means a user who switches away for 5–10 minutes may return to a near-expired token. A single network hiccup during renewal would then cause a logout. 30 minutes provides a comfortable window without meaningfully weakening the security posture given the 7-day refresh token.

Token rotation is enabled (ROTATE_REFRESH_TOKENS = True) and old refresh tokens are blacklisted after use (BLACKLIST_AFTER_ROTATION = True), so a stolen refresh token cannot be used more than once.


Proactive Renewal

Rather than waiting for a 401 response, the app renews the access token before it expires. Three paths trigger renewal:

Path Trigger Implemented in
Startup App launch or access token change useAuthManageruseEffect on accessToken
Periodic Every 7 minutes while authenticated useAuthManageruseInterval
Foreground resume App returns to foreground useAuthManagerAppState listener

Each path calls verifySession(TOKEN_RENEWAL_TIMER) (7-minute buffer) to determine whether renewal is needed. If the current time is within 7 minutes of expiry — or past it — renewal is triggered.

A fourth path handles reactive renewal: if an API call returns 401 despite a seemingly valid token (clock skew, race condition), the Axios interceptor in HttpClient attempts a refresh before retrying the original request.


Retry Logic & Failure Handling

All renewal paths use a shared retry function with 3 attempts and exponential backoff (1 s → 3 s → 9 s).

Failures are classified into two categories before deciding whether to log out:

Error type Examples Action
Transient Network timeout, no connection, 5xx, 408, 429 Retry up to 3 times; if all retries exhausted, keep session and report a warning to Sentry
Permanent 401 or 403 from the refresh endpoint (refresh token expired or revoked) Logout immediately, no retries

This means a flaky network connection during a periodic renewal or foreground resume will not log the user out — the session is preserved and the next renewal attempt will succeed once connectivity is restored.


Parallel Refresh Deduplication

When the app returns to foreground, SWR revalidates all cached queries simultaneously. If the access token is expired, this produces multiple concurrent 401 responses. Without deduplication, each 401 would trigger its own refresh attempt — only one can succeed (token rotation blacklists the old refresh token), and the others would fail and potentially cause a logout.

Two layers of deduplication prevent this:

  1. refreshSessionPromise (module-level in HttpClient) — the first 401 to reach the interceptor creates a single in-flight refresh promise. All subsequent 401s from the same burst await that same promise rather than starting their own.

  2. moize.promise on renewSession (15-second cache) — deduplicates calls across all paths (interceptor, periodic timer, foreground AppState listener) at the store level. A refresh that completes via one path will be returned as-is to any other path that fires within 15 seconds.


Relevant Files

File Role
src/stores/auth.ts Token state, renewSession (moized), revokeSession, useAuthManager hook
src/services/HttpClient.ts Axios interceptor, reactive 401 refresh, refreshSessionPromise deduplication
src/services/AuthService.ts refreshToken() — calls the Django SimpleJWT refresh endpoint
src/common/StaleWhileRevalidate.tsx SWR global config — foreground token check before revalidation
Base-API/base/settings.py SIMPLE_JWT — token lifetimes, rotation, blacklist settings