Skip to content

fix(mobile): only log out on a genuinely rejected refresh token (port #2812)#2833

Open
Gilbert09 wants to merge 3 commits into
mainfrom
posthog-code/mobile-refresh-token-logout
Open

fix(mobile): only log out on a genuinely rejected refresh token (port #2812)#2833
Gilbert09 wants to merge 3 commits into
mainfrom
posthog-code/mobile-refresh-token-logout

Conversation

@Gilbert09

Copy link
Copy Markdown
Member

Summary

Ports desktop #2812 ("fix(auth): Log out on rejected refresh token instead of stalling startup") to the React Native / Expo app at apps/mobile.

Mobile has its own OAuth refresh path (not the core OAuthService). refreshAccessToken threw a generic Error on any non-ok response, and initializeAuth deleted the stored tokens and signed the user out on any thrown error. A transient network or server blip at startup therefore logged the user out unnecessarily — the exact bug #2812 guards against.

Changes

  • lib/oauth.tsrefreshAccessToken now classifies the failure and throws a TokenRefreshError carrying an errorCode, mirroring desktop's categories:

    • 401/403, or 400 with invalid_grant/invalid_tokenauth_error (token genuinely dead)
    • 5xxserver_error (transient, retryable)
    • thrown fetch (DNS/TLS/socket) → network_error
    • other 400s (e.g. invalid_client/invalid_request, config bugs) → unknown_error

    A small helper parses the OAuth error code from the JSON body and tolerates non-JSON bodies.

  • stores/authStore.tsinitializeAuth now only deletes tokens / signs out on auth_error. On transient or config failures it keeps the stored session, so the next request's existing per-request authedFetch retry path (mobile fix(mobile): auto-refresh on PostHog 401/403 auth failures #2408) can recover. Also deduped the repeated auth-clearing set({...}) payload into a shared CLEARED_AUTH_STATE.

The proactive scheduleTokenRefresh handler is intentionally left as-is: it already keeps the session on every error (it only logs), so it never had the bug, and logging out from a background timer on a still-valid access token would be a new behavior beyond this port.

Tests

  • lib/oauth.test.ts (new) — covers the classification: 401/403 → auth_error, 400 invalid_grant/invalid_tokenauth_error, 400 invalid_client/invalid_requestunknown_error, 5xx → server_error, thrown fetch → network_error.
  • stores/authStore.test.tsinitializeAuth with an expired token: auth_error → tokens deleted + signed out; server_error/network_error/unknown_error → session kept, no token deletion.

pnpm --filter @posthog/mobile lint and the auth/api test suites pass.

…2812)

Mobile had its own OAuth refresh path that threw a generic Error on any
non-ok response, so a transient network/server blip at startup deleted the
tokens and signed the user out.

Classify the refresh failure in `refreshAccessToken` and only sign out when
the refresh token is genuinely dead:
- 401/403, or 400 with `invalid_grant`/`invalid_token` -> auth_error -> sign out
- 5xx -> server_error -> keep session
- network failure -> network_error -> keep session
- other 400s (e.g. invalid_client) -> unknown_error/config -> keep session

`initializeAuth` now keeps the stored session on transient/config failures so
the per-request `authedFetch` retry can recover, and only deletes tokens on a
genuine auth_error.

Ports desktop #2812 to apps/mobile.

Generated-By: PostHog Code
Task-Id: c274cae6-c3d7-46f4-bfe1-96b3514dbc94
@Gilbert09 Gilbert09 requested a review from a team June 22, 2026 10:45
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit 074fb2f.

@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/mobile/src/features/auth/stores/authStore.ts:370-372
The spread `{ ...CLEARED_AUTH_STATE }` in `logout` creates an identical shallow copy with no additions. `set` accepts a partial state directly, so the spread is superfluous here. The other two call sites correctly add `isLoading: false` to the spread, which is why the spread is needed there — but not here.

```suggestion
        set(CLEARED_AUTH_STATE);
      },
    }),
```

Reviews (1): Last reviewed commit: "fix(mobile): only log out on a genuinely..." | Re-trigger Greptile

Comment thread apps/mobile/src/features/auth/stores/authStore.ts Outdated
… logout

`set` accepts the partial state directly, so `set(CLEARED_AUTH_STATE)` is
equivalent to spreading it with no additions. The two other call sites keep
the spread because they add `isLoading: false`.

Generated-By: PostHog Code
Task-Id: c274cae6-c3d7-46f4-bfe1-96b3514dbc94
Generated-By: PostHog Code
Task-Id: b11bf371-325f-4f8b-904d-0209ab60c582
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.

1 participant