Problem
Section titled “Problem”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:
- 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.
- 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.
- The refresh token itself expired (its TTL is hit) — silent refresh fails, the SDK doesn't loop; the user must sign in again.
- The refresh token was revoked — by an admin, by reuse detection, or by the user from another device.
Resolution
Section titled “Resolution”For users who just want to keep working
Section titled “For users who just want to keep working”The SDK's getAccessToken() method handles this transparently when called fresh:
const token = await getAccessToken() // returns a fresh token, refreshing if neededconst 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.
For clock-drift
Section titled “For clock-drift”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.
How to tell which cause
Section titled “How to tell which cause”Catch the error in your SDK call. The error's code is specific:
| code | Cause |
|---|---|
token_expired | Access token past exp; refresh should resolve it |
session_expired | Refresh token TTL hit; user must sign in again |
session_revoked | Refresh token invalidated (reuse detection, admin) |
silent_auth_failed | Network 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.
Prevention
Section titled “Prevention”- 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.