Skip to content

Access token expired in the middle of a session

An API call fails with 401 Unauthorized and error: token_expired. The user is still signed in (they didn't click logout, their tab is open), but the access token they hold is past its exp.

The most common causes, in order:

  1. The tab was inactive long enough that the silent refresh paused — most browsers throttle background timers, so a tab left for hours without focus doesn't refresh on schedule.
  2. The clock on the user's device is meaningfully off — the SDK trusts the device clock when scheduling refresh. A device 10 minutes ahead refreshes 10 minutes early; a device 10 minutes behind never realises the token is expired.
  3. The refresh token itself expired (its TTL is hit) — silent refresh fails, the SDK doesn't loop; the user must sign in again.
  4. The refresh token was revoked — by an admin, by reuse detection, or by the user from another device.

The SDK's getAccessToken() method handles this transparently when called fresh:

const token = await getAccessToken() // returns a fresh token, refreshing if needed
const res = await fetch(url, { headers: { 'Authorization': `Bearer ${token}` } })

If your code grabs the token once at boot and reuses it indefinitely, you'll hit token_expired after the first hour. Always call getAccessToken() immediately before each API call.

For background tabs that need to keep refreshing

Section titled “For background tabs that need to keep refreshing”

Two options:

  • Accept that the token will expire when the tab is inactive, and have your code re-call getAccessToken() when the user returns to the tab. The Page Visibility API event (document.addEventListener('visibilitychange', ...)) is the right hook.
  • Move the refresh to a Service Worker, which isn't subject to background-throttle.

For most apps, the first is fine — refreshing only when the tab is active is a reasonable energy posture.

The SDK adds a 60s leeway window to refresh timing — it refreshes 60s before exp, not at exp. If your users' devices are reliably more than 60s off, expand the leeway via the provider's silentRefreshLeeway prop.

<IntelliAuthProvider silentRefreshLeeway={120} ... />

When the refresh token has expired or been revoked

Section titled “When the refresh token has expired or been revoked”

The SDK's error will be session_expired or session_revoked, not token_expired. Show a "please sign in again" message and redirect through sign-in. This is the correct behaviour — long-lived sessions are bounded for a reason.

Catch the error in your SDK call. The error's code is specific:

codeCause
token_expiredAccess token past exp; refresh should resolve it
session_expiredRefresh token TTL hit; user must sign in again
session_revokedRefresh token invalidated (reuse detection, admin)
silent_auth_failedNetwork failure during refresh; retry

Branch your UI accordingly. Calling loginWithRedirect() for session_expired is correct; for silent_auth_failed, retrying the API call (which will trigger a fresh refresh) is correct.

  • Always call getAccessToken() right before the API call.
  • Configure a sensible refresh-token TTL on the application (default 14 days for browser; tune up for desktop / mobile).
  • Surface a friendly "your session has ended" UI rather than a generic 401 toast.