Skip to content

fix: random logouts on unstable network (only clear session on server rejection)#43

Merged
joshuabaker merged 2 commits into
mainfrom
fix/offline-logout
Jul 2, 2026
Merged

fix: random logouts on unstable network (only clear session on server rejection)#43
joshuabaker merged 2 commits into
mainfrom
fix/offline-logout

Conversation

@joshuabaker

@joshuabaker joshuabaker commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Fixes #34 — random logouts on unstable/no network.

Root cause

getAccessToken's catch-all treated every refresh failure as a dead session: any error (including plain network failures from performAsync) triggered storage.clear() + set(emptySession) — destroying a perfectly valid refresh token. Because the auto-restore path calls getAccessToken({ forceRefresh: true }) on every cold launch, opening the app offline guaranteed a logout; mid-session refreshes near token expiry had the same hole. The storage.clear() made it irreversible — tokens were gone even once the network recovered.

Fix

  • getAccessToken: only clears the session when the server actually rejects the refresh (TokenError, e.g. invalid_grant). Transient failures (network, server hiccups) keep tokens and state so the next call retries naturally — and return the stored access token when it's still fresh (TokenResponse.isTokenFresh), so an offline cold start still yields a usable token. Corrupt storage / invalid response shapes still clear, as before.
  • signOut (inverse bug found during review): revocation is now best-effort — previously an offline revoke threw before storage.clear(), so users couldn't sign out without a connection. Local session now always clears.

Tests

Replaced the old always-clear expectation with:

  • server rejection (TokenError) → session cleared (unchanged intent)
  • network error → session kept, storage untouched, returns null when stale
  • network error + still-fresh token → returns stored token, stays signed in
  • offline signOut → resolves and clears locally

29/29 passing; coverage 96% stmts / 94% branches (thresholds 80/80). Full CI mirror run locally: build, type-check, format, publint, attw — all green.

Includes a patch changeset (→ 0.1.3) and updates the CLAUDE.md design notes.

Also: single-flight refresh (second commit)

Concurrent getAccessToken() calls each fired their own refresh. WorkOS refresh tokens are single-use, so parallel refreshes race and the loser gets invalid_grant — a genuine TokenError, indistinguishable from remote revocation → spurious logout. Now all concurrent callers share one in-flight refresh promise (reset in finally, so a failed refresh doesn't wedge future calls). This closes the last forced-logout path that isn't a real revocation.

Additional tests: concurrent calls → exactly one refresh request; failed refresh → next call starts a fresh one. 31/31 passing, coverage 96.6% stmts / 94.2% branches.

@changeset-bot

changeset-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 565a708

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
authkit-react-native Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@joshuabaker joshuabaker merged commit 469d67d into main Jul 2, 2026
2 checks passed
@joshuabaker joshuabaker deleted the fix/offline-logout branch July 2, 2026 12:16
@joshuabaker joshuabaker mentioned this pull request Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Experiencing logouts when network connection is unstable

1 participant