Repository navigation
[Due for payment 2026-07-21] [$500] [Mobile] Okta SSO requires double login after idle timeout — app freezes on SAML session handoff #86705
Description
Activity
- addedBugSomething is broken. Auto assigns a BugZero manager.Something is broken. Auto assigns a BugZero manager.
on Mar 31, 2026 Proposal
What is the root cause of that problem?
When a mobile SAML/Okta user returns from the in-app browser (after completing MFA), a race condition occurs between two competing flows:
- AppState "became active" listener — fires immediately when the app resumes from the background and triggers
reconnectApp()using the OLD expiredauthToken(NetworkConnection.ts:322-328,AuthScreensInitHandler.tsx:75-103) - SAML callback —
openAuthSessionAsyncpromise resolves with the newshortLivedAuthTokenand callssignInWithShortLivedAuthToken()(SAMLSignInPage/index.native.tsx:57-61)
If Path 1 fires before Path 2's optimistic Onyx update sets
isAuthenticatingWithShortLivedToken=true, the sequence is:reconnectApp()gets a 407 (NOT_AUTHENTICATED) from the server- Reauthentication middleware tries to re-authenticate with stored auto-generated credentials
- For SAML users, these credentials may be empty/invalid (they signed in via SAML, not password)
- Reauthentication fails →
redirectToSignIn()→Onyx.clear()wipes ALL data including any newly set authToken (Authentication.ts:178-194,SignInRedirect.ts:84-118) - AuthScreens unmounts →
cleanupSession()→HttpUtils.cancelPendingRequests()cancels any in-flightsignInWithShortLivedAuthTokenrequest (Session/index.ts:1008-1023)
The second login always works because the app is in a clean state with no competing reconnection callbacks.
Supporting evidence:
Authentication.ts:133-140has the "redirect to sign-in for missing credentials" logic commented out with a reference to#fireroom-2026-01-28-user-signout, confirming this exact pattern was already suspected.This is mobile-only because: (a) the in-app browser backgrounds the app, triggering AppState callbacks on resume, and (b) desktop doesn't use
openAuthSessionAsync.What changes do you think we should make in order to solve the problem?
In
SAMLSignInPage/index.native.tsx, setisAuthenticatingWithShortLivedToken=truein Onyx before opening the in-app browser (i.e., before theopenAuthSessionAsynccall). This blocks the reauthentication middleware from racing against the SAML callback. Reset the flag tofalseif the browser is cancelled or fails.This ensures that when the app returns to the active state and
reconnectApp()fires with the expired token, any resulting 407 response will hit theisAuthenticatingWithShortLivedTokenguard inAuthentication.ts:110-117and abort reauthentication instead of triggeringredirectToSignIn.What alternative solutions did you explore? (Optional)
-
Suppress reconnection callbacks during SAML flow — Unregister the NetworkConnection reconnection listener before opening the in-app browser and re-register after. This is more invasive and could miss legitimate reconnection events if the network was actually lost.
-
Add retry logic in the SAML callback error path — Currently if the guard
!account?.isLoading && credentials?.login && shortLivedAuthTokenfails atSAMLSignInPage/index.native.tsx:57, the flow falls through toclearSignInData()which logs the user out. Adding a retry here would be a workaround rather than fixing the root race condition. -
Delay the AppState "became active" reconnection callback — Add a debounce or short delay before firing
reconnectApp()to give the SAML callback time to set its guard flag. This is fragile and timing-dependent.
Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR,@MelvinBot <your feedback>to refine this analysis, or explain why you are rejecting Melvin's proposal.- AppState "became active" listener — fires immediately when the app resumes from the background and triggers
- addedExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributor
on Mar 31, 2026 - addedHelp WantedApply this label when an issue is open to proposals by contributorsApply this label when an issue is open to proposals by contributors
on Mar 31, 2026 Triggered auto assignment to Contributor-plus team member for initial proposal review - @bernhardoj (
External)- changed the title
[-][Mobile] Okta SSO requires double login after idle timeout — app freezes on SAML session handoff[/-][+][$250] [Mobile] Okta SSO requires double login after idle timeout — app freezes on SAML session handoff[/+]on Mar 31, 2026 Job added to Upwork: https://www.upwork.com/jobs/~022038834681163684626
Proposal
Please re-state the problem that we are trying to solve in this issue.
After an Okta SSO idle timeout on mobile, completing SAML re-authentication in the in-app browser causes the app to freeze and then log the user out, forcing a redundant second login. The second attempt always succeeds.
What is the root cause of that problem?
When the app resumes from background with an expired SAML session, the
Reauthenticationmiddleware insrc/libs/Middleware/Reauthentication.ts#L96unconditionally callsreauthenticate()for any 407 response. For SAML users, this cascades intoAuthenticate()inAuthentication.tswhich fails atrequireParameters(because SAML users have no storedautoGeneratedLogin/autoGeneratedPassword), and the catch block firesredirectToSignIn()→Onyx.clear(). ThisOnyx.clear()is destructive — it wipes all app data including any new session being established by the concurrent SAML re-authentication callback fromopenAuthSessionAsync. The middleware has no awareness that the current user is a SAML user who cannot reauthenticate with stored credentials, so it always attempts the doomed reauthentication path.What changes do you think we should make in order to solve the problem?
We should add a SAML-awareness check in
src/libs/Middleware/Reauthentication.tsso that when a 407 is received for a SAML user, we skip the fullreauthenticate()flow (which destructively callsOnyx.clear()) and instead just invalidate the auth tokens, letting React's navigation naturally redirect to sign-in:// Add at module level in Reauthentication.ts import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; let signedInWithSAML = false; Onyx.connectWithoutView({ key: ONYXKEYS.SESSION, callback: (val) => { signedInWithSAML = !!val?.signedInWithSAML; }, }); // Insert before line 96 (`return reauthenticate(...)`) if (signedInWithSAML) { // SAML users cannot reauthenticate with stored credentials. // Clear only auth tokens (not full Onyx.clear) to avoid destroying // a concurrent SAML re-auth session from the in-app browser callback. Onyx.merge(ONYXKEYS.SESSION, {authToken: null, encryptedAuthToken: null}); setIsAuthenticating(false); if (isFromSequentialQueue) { return data; } if (request.resolve) { request.resolve(data); } return data; }
This avoids the destructive
Onyx.clear()for SAML users while still properly invalidating the expired session, allowing the SAML re-authentication flow to complete without interference. The subsequentopenApp()call after successful SAML login will refresh all stale data.Contributor details
Your Expensify account email: trasnake87@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~010f770315ab181656✅ Contributor details stored successfully. Thank you for contributing to Expensify!
Proposal
Please re-state the problem that we are trying to solve in this issue.
On mobile, when a SAML/Okta SSO user returns to the app after an idle timeout and completes re-authentication + MFA in the in-app browser, the app freezes briefly, logs the user out, and forces a second login. The second login always succeeds.
What is the root cause of that problem?
When the in-app browser closes and the app resumes from a backgrounded state, two competing paths fire concurrently:
Path 1 — App resume reconnection:
AppStateMonitor.addBecameActiveListener(registered bylistenForReconnect()) fires immediately when the app transitions from background/inactive → active. This triggerstriggerReconnectionCallbacks, which callsreconnectApp()with the old expiredauthToken:App/src/libs/NetworkConnection.ts
Lines 322 to 328 in 2b517db
function listenForReconnect() { Log.info('[NetworkConnection] listenForReconnect called'); AppStateMonitor.addBecameActiveListener(() => { triggerReconnectionCallbacks('app became active'); }); } App/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx
Lines 102 to 103 in 2b517db
NetworkConnection.listenForReconnect(); NetworkConnection.onReconnect(() => handleNetworkReconnect()); Path 2 — SAML callback: The
openAuthSessionAsyncpromise resolves with the redirect URL containing the newshortLivedAuthToken, andhandleNavigationStateChangecallssignInWithShortLivedAuthToken():App/src/pages/signin/SAMLSignInPage/index.native.tsx
Lines 57 to 61 in 2b517db
if (!account?.isLoading && credentials?.login && shortLivedAuthToken) { Log.info('SAMLSignInPage - Successfully received shortLivedAuthToken. Signing in...'); signInWithShortLivedAuthToken(shortLivedAuthToken, true); return; } The
reauthenticate()function has an existing guard that checksisAuthenticatingWithShortLivedTokenand aborts if true:App/src/libs/Authentication.ts
Lines 110 to 117 in 2b517db
// Prevent re-authentication if authentication with shortLiveToken is in progress if (isAuthenticatingWithShortLivedToken) { Log.hmmm('[Reauthenticate] Authentication with shortLivedToken is in progress. Re-authentication aborted.', { command, isSupportAuthTokenUsed, }); return Promise.resolve(false); } However, this flag is only set inside
signInWithShortLivedAuthToken()'s optimistic data (viagetShortLivedLoginParamsat line 171), which runs after the browser returns — too late to block the Path 1 reauthentication that fires simultaneously on app resume.App/src/libs/actions/Session/index.ts
Lines 154 to 175 in 2b517db
function getShortLivedLoginParams(isSupportAuthTokenUsed = false, isSAML = false) { const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.ACCOUNT | typeof ONYXKEYS.SESSION | typeof ONYXKEYS.HYBRID_APP>> = [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.ACCOUNT, value: { ...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true, }, }, // We are making a temporary modification to 'signedInWithShortLivedAuthToken' to ensure that 'App.openApp' will be called at least once { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.SESSION, value: { signedInWithShortLivedAuthToken: true, signedInWithSAML: isSAML, isAuthenticatingWithShortLivedToken: true, isSupportAuthTokenUsed, }, }, ]; If Path 1's
reauthenticate()runs before Path 2 sets the guard, it proceeds with auto-generated credentials. For SAML users whose IdP session has expired server-side, this authentication fails — triggeringredirectToSignIn()which callsOnyx.clear(), wiping all session state (including any in-flight SAML authentication) and cancelling pending requests viacleanupSession():App/src/libs/actions/SignInRedirect.ts
Lines 84 to 105 in 2b517db
return Onyx.clear(keysToPreserve).then(() => { if (CONFIG.IS_HYBRID_APP) { resetSignInFlow(); HybridAppModule.signOutFromOldDot(); } clearAllPolicies(); // When logging out from imported state, reset shouldForceOffline to false and clear the imported state flag // so the user can log back in if (currentIsUsingImportedState) { Onyx.merge(ONYXKEYS.NETWORK, {shouldForceOffline: false}); Onyx.merge(ONYXKEYS.IS_USING_IMPORTED_STATE, false); } if (!errorMessage) { return; } // `Onyx.clear` reinitializes the Onyx instance with initial values so use `Onyx.merge` instead of `Onyx.set` Onyx.merge(ONYXKEYS.SESSION, {errors: getMicroSecondOnyxErrorWithMessage(errorMessage)}); }); } App/src/libs/actions/Session/index.ts
Lines 1008 to 1023 in 2b517db
function cleanupSession() { Pusher.disconnect(); Timers.clearAll(); Welcome.resetAllChecks(); MainQueue.clear(); HttpUtils.cancelPendingRequests(); PersistedRequests.clear(); NetworkConnection.clearReconnectionCallbacks(); SessionUtils.resetDidUserLogInDuringSession(); resetNavigationState(); clearCache().then(() => { Log.info('Cleared all cache data', true, {}, true); }); clearCachedAttachments(); clearSoundAssetsCache(); } This is mobile-only because: (a)
openAuthSessionAsyncbackgrounds the app on Android and puts it inactive on iOS, triggering AppState callbacks on resume, and (b) desktop uses a different SAML implementation that doesn't use the in-app browser.The second login always works because the app is in a clean state with no competing reconnection flow — the
AppStateMonitor.becameActivelistener has already fired for that app resume cycle.What changes do you think we should make in order to solve the problem?
In
SAMLSignInPage/index.native.tsx, setisAuthenticatingWithShortLivedTokentotruein Onyx before callingopenAuthSessionAsync. This ensures the guard inreauthenticate()is active for the entire duration of the SAML browser session, blocking any reconnection-triggered reauthentication from racing with the SAML callback.Reset the flag to
falseon all non-success exit paths:- If the browser is cancelled (
response.type !== 'success') - If the browser throws an error
- If
handleNavigationStateChangefalls through (condition at line 57 fails)
When
signInWithShortLivedAuthToken()is called on the success path, its own optimistic data takes over flag management (setstrueoptimistically, resets infinallyData), so no manual reset is needed there.What alternative solutions did you explore? (Optional)
-
Add a synchronous setter in
Authentication.tsinstead of using Onyx merge — directly setting the module-levelisAuthenticatingWithShortLivedTokenvariable. This would avoid any theoretical async propagation delay fromOnyx.merge→connectWithoutViewcallback. However, since the Onyx merge happens beforeopenAuthSessionAsync(before the app backgrounds for seconds/minutes), the callback has ample time to propagate. The additional complexity of a synchronous setter is not warranted. -
Uncomment the credentials guard in
Authentication.ts:133-140— the code that redirects to sign-in whenautoGeneratedLogin/autoGeneratedPasswordare missing is currently commented out (investigating#fireroom-2026-01-28-user-signout). Uncommenting it would make reauthentication fail faster for SAML users without stored credentials, but it doesn't address the core race condition and could reintroduce the user-signout issue being investigated.
Compare branch: https://github.com/Expensify/App/compare/main...wildan-m:App:wildan/86705-saml-sso-double-login?expand=1
- If the browser is cancelled (
Report - Okta SSO requires double login after idle timeout — app freezes on SAML session handoff
Please re-state the problem that we are trying to solve in this issue.
After an Okta SSO session times out (~4 hours idle), mobile users who successfully complete Okta re-authentication and MFA in the in-app browser are silently logged out and redirected to the login screen instead of being returned to the app. The second login attempt always succeeds. This affects all mobile users on SAML/Okta SSO and is not reproducible on desktop.
What is the root cause of that problem?
There are two compounding issues in the client-side SAML callback handling:
Primary : stale account.isLoading gate in SAMLSignInPage.tsx: handleNavigationStateChange only calls signInWithShortLivedAuthToken if !account?.isLoading && credentials?.login && shortLivedAuthToken are all truthy. When a session expires, Onyx sets account.isLoading = true as part of the session cleanup. By the time the Okta callback fires with a fresh shortLivedAuthToken, account.isLoading is still true from the prior session teardown so the condition fails, falls through to clearSignInData(), and the user is logged out.
Secondary : getLastShortAuthToken() guard in LogInWithShortLivedAuthTokenPage.tsx: This guard exists to prevent replaying stale deep links, but it can incorrectly block a legitimate re-authentication if the newly issued token matches the cached last token. When it fires, it silently returns without signing in.What changes do you think we should make in order to solve the problem?
[credentials?.login, account?.isLoading, translate],
[translate],if (!account?.isLoading && credentials?.login && shortLivedAuthToken) {
if (shortLivedAuthToken) {if (token && !account?.isLoading) {
if (token) {(Remove this line)const [account] = useOnyx(ONYXKEYS.ACCOUNT); if (!account?.isLoading && authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT) {
if (authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT) {App/src/libs/actions/Session/index.ts
Line 987 in b078372
} NetworkStore.setLastShortAuthToken(''); }
What alternative solutions did you explore?
N/A
161 remaining items
Load more actions@MelvinBot payment-review
MODE: <closed-no-pr | payment-due>
JOB_PRICE: 250 # override if the title bounty differsRun the C+ payment framework for this issue and post a structured, evidence-cited
recommendation. Rules:- Suggest only — never auto-pay. A human confirms.
- Every point / adjustment MUST cite a specific comment permalink. No permalink → no point.
- Round every %→$ result up to the nearest $25: amount = ceil((pct × JOB_PRICE) / 25) × 25.
If MODE = closed-no-pr:
- Run the eligibility gate; if it fails, or the token-saving skip-gate trips
(<~8 non-bot comments AND no C+ engagement markers, or closed <48h, or spam/invalid),
return "$0 — skipped" with a one-line reason and STOP. - Bucket the close reason (A/B/C), score the 5 factors 0–10 with permalinks.
- Payout = 25% of JOB_PRICE if score ≥ 8, else $0.
If MODE = payment-due:
- Start at 100% of JOB_PRICE.
- Decreases: for any regression, read the offending PR + issue and decide if it's
truly attributable to this author/reviewer. If yes, −50% per verified regression.
If fault is unclear, DO NOT deduct — flag for human review. - Increases: evaluate the C+ increase framework (multiple PRs, additional fixes,
scope expansion, core-flow changes, deploy blockers audited, performance,
test coverage, files touched), each backed by a permalink.
Output: recommended $ amount, per-line reasoning with permalinks, confidence
(High/Med/Low), and any manual-review flags.Reacted by melvin-bot💸 Payment Review — C+ Reviewer (
thelullabyy)Suggestion only — not an authorization to pay. A human must confirm.
Mode:
payment-due· JOB_PRICE: $500 (overridden from $250 — title bounty is[$500])🧾 Recommended payout: $500 (100% of JOB_PRICE)
Step % $ Basis Base 100% $500 Merged + deployed fix, C+ role Regression deductions 0% –$0 No verified regressions Increase framework 0% +$0 No qualifying uplift Total 100% $500 Rounding:
ceil((1.00 × 500) / 25) × 25 = $500.
Base (100% → $500)
thelullabyyperformed the C+ review that led to a merged, deployed fix:- Reviewed the competing proposals and selected the approved solution — comment.
- Was assigned the Reviewer role — offer.
- Is the assignee on the merged PR Expensify/App#95017 (merged 2026-07-10 by
grgia). - Completed the BugZero Checklist — comment.
- Fix deployed to production in
9.4.33-8on 2026-07-14.
Regression deductions (–$0)
- No
Regressionlabel on the issue; labels areReviewing, External, Daily, Awaiting Payment, Bug. - 7-day regression period ran 2026-07-14 → 2026-07-21 with no regression reports in the thread. Title reflects
[Due for payment 2026-07-21]. → No deduction.
Increase framework (+$0)
Evaluated each lever; none clears the bar for an uplift beyond base:
- Multiple PRs: The reviewer did catch a real defect in the first attempt — the guard not being cleared on the no-JSON early-return path (comment) — and directed the follow-up (comment). But Expensify/App#94082 was never merged; it was superseded by Expensify/App#95017. Reviewing a superseded PR + its replacement for the same single fix is normal review scope, not a scope-expansion bonus.
- Files touched / test coverage / core-flow: The merged PR touches 3 files and adds a regression test (
tests/actions/SessionTest.ts) — but that is the PR author's work, not a reviewer uplift trigger. - Deploy blockers / performance: None audited or applicable.
→ No increase.
🚩 Manual-review flags (out of framework scope, but worth a human eye before paying)
- Contributor-side payment is ambiguous — this review only covers the C+ reviewer. Both PRs in the merged lineage (#94082 → #95017) were authored by
MelvinBot, not a human contributor. The competing human PR #94396 bytrasnake87was closed unmerged. A human should decide whether any Contributor bounty is owed and to whom. - Reviewer could not independently test the SAML flow.
thelullabyyrepeatedly noted being unable to set up a SAML session and accepted the fix on theoretical grounds pending QA regression — comment. Confirm QA/regression verification occurred before final sign-off.
Confidence: High on the base ($500) and the clean regression window. The two flags above are the only items needing human judgment.
@MelvinBot is @trasnake87 due payment?
Reacted by melvin-botIs
trasnake87due payment?Short answer: not under the standard contributor rule — their PR was never merged and they aren't a co-author of the one that was. But they were formally assigned and their bug-catch fed directly into the merged fix, so this is a discretionary call I'd flag for a human rather than a clean "no."
Evidence
Points against a standard bounty:
- Their proposal was not the approved one — the reviewer selected Melvin's proposal on 2026-06-19, before
trasnake87was assigned. - Their PR Expensify/App#94396 was closed unmerged on 2026-06-26.
- The merged PR Expensify/App#95017 was authored by
MelvinBot; its only co-authors areroryabrahamandthelullabyy— nottrasnake87. - The reviewer explicitly declined their offer to help finish it: "Thank you but I think I can handle it with Melvin" (comment).
Points for a discretionary payment:
- They were formally assigned as the contributor on 2026-06-23 and asked to open a PR (comment).
- They flagged the exact no-
jsonearly-return guard-leak edge case on 2026-07-01 00:46 — ~13 min before the reviewer directed Melvin to open #95017 to fix precisely that. That fix ("Additional fix in this PR") is the delta Clear SAML short-lived-token guard on the no-JSON early-return path (based on #94082) #95017 added on top of Fix mobile SAML/Okta double login on session resume #94082, and it's what got merged and deployed.
My recommendation for the human:
- If applying the standard rule (payment follows the merged PR's author/co-authors),
trasnake87is $0 — PR closed, not a co-author of Clear SAML short-lived-token guard on the no-JSON early-return path (based on #94082) #95017. - If you want to recognize the formal assignment + the guard-leak catch that shaped the merged fix, a discretionary partial payment is defensible. Your call — I can't authorize it.
Confidence: High on the facts (PR closed, not a co-author, proposal not approved). The pay/no-pay decision itself is a judgment call for you.
- Their proposal was not the approved one — the reviewer selected Melvin's proposal on 2026-06-19, before
@thelullabyy do you think @trasnake87 is due payment? If so, how much? Please provide reasoning, details and/or links for support (I'm testing the above and might want to add something to Melv's skill/rules)
@mallenexpensify Sorry, I'm not sure whether @trasnake87 is due or not. This case is new to me, here is the context:
- We decided to go with Melvin bot proposal
- We was struggling with testing
- @trasnake87 raised a comment that he was able to test the issue with evidences
- We assigned @trasnake87 to open PR (same solution with Melvin) with fully tested evidences
- I reviewed the PR and found that the testing was not a acceptable test
- We decided to go back with Melvin and closed @trasnake87's PR
I think I will let you make a call here @mallenexpensify
Payment Summary
Contributor+: @thelullabyy paid $500 via Upwork
@trasnake87 comment and tag me if you think you're due payment and I'll reopen the GH. Thx
@mallenexpensify Wrong Contributor+
Thanks @DylanDylann , updated.
laurenreidexpensify commented
on Aug 17, 2026 ContributorMore actions$500 approved @thelullabyy New Expensify
@mallenexpensify a correction for the record, since you mentioned using this thread to tune Melvin's payment rules. It's on me: Melvin's summary credits me with catching the no-
jsonguard case, but the Codex review on #94082 flagged it first, on 06-26. My 07-01 comment also overstated things: #94396 as reviewed had the same gap, and I only added that fix to the branch after the Codex review, once the PR was already closed.Sorry for the confusion, and sorry I missed your note when the issue closed.
Reacted by Matt Allen
If you found this issue via BugZero, please follow the BugZero triage guidelines in the wiki article here. In particular:
Version Numberfield (you can find this in the App Deployer or GitHub Releases) and change the template title.Regressionand add theOriginal Author - @<author>andRegression Author - @<author>labels after determining the source of the regression (you can usegit bisect).Needs Investigationand tagging relevant engineers.Action Performed:
Expected Result:
User is successfully authenticated and returned to the app with a persisted session — no additional login required.
Actual Result:
App freezes briefly after returning from the in-app browser, then logs the user out and redirects to the Expensify login screen. User must log in a second time (including Okta MFA). The second login works as expected.
Workaround:
User can log in a second time — the second attempt always succeeds.
Platforms:
Which of our officially supported platforms is this issue occurring on?
Version Number: Latest (reported on 9.3.39-3+ and still occurring)
Reproducible in staging?: Unknown
Logs: N/A
Notes/Photos/Videos:
Additional Context
Technical Context from Investigation on Expensify/Expensify#613615
The mobile SAML re-authentication flow relies on a multi-redirect chain that passes a 1-minute
shortLivedAuthTokenback to the app via a Universal Link / deep link. When the app is backgrounded or in a transitional state during this handoff, the token can expire or the deep link can fail to fire, causing the first login attempt to silently fail.Several server-side fixes have already been merged (Auth #19751, Web-Expensify #50652) addressing
samlInfiniteMobileSessionsflag preservation. However, the client-side handling of the SAML deep link callback when the app is resuming from a backgrounded state needs investigation — this is likely where the mobile-specific failure occurs.Key areas to investigate in the App:
isSAML=true) when it has an expired session?shortLivedAuthTokenexchange when resuming from background?Upstream issue: https://github.com/Expensify/Expensify/issues/613615
Issue Owner
Current Issue Owner: @mallenexpensifyUpwork Automation - Do Not Edit