Skip to content

Clear SAML short-lived-token guard on the no-JSON early-return path (based on #94082)#95017

Merged
grgia merged 4 commits into
mainfrom
claude-samlSsoDoubleLoginClearGuard
Jul 10, 2026
Merged

Clear SAML short-lived-token guard on the no-JSON early-return path (based on #94082)#95017
grgia merged 4 commits into
mainfrom
claude-samlSsoDoubleLoginClearGuard

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

This PR is based on #94082 and includes all of its changes, plus an additional fix requested in review.

Background (from #94082). On mobile, when a SAML/Okta SSO user returns to the app after their IdP session has gone idle and completes re-authentication + MFA in the in-app browser, the app briefly freezes, logs them out, and forces a second login. The root cause is a race on app resume: opening the in-app browser backgrounds the app, and when it closes reconnectApp() fires with the now-expired authToken, gets a 407, and reauthenticate() takes the isSAMLRequired branch and calls redirectToSignIn() — wiping the session before the SAML callback can sign the user in. The fix sets the isAuthenticatingWithShortLivedToken guard (RAM_ONLY_IS_AUTHENTICATING_WITH_SHORT_LIVED_TOKEN) to true before opening the in-app browser so reauthenticate() aborts for the entire SAML browser session. The guard is reset to false on the cancel/error/callback-failure paths, and on success signInWithShortLivedAuthToken() takes over flag management.

Additional fix in this PR (review suggestion). handleNavigationStateChange() has an early return when the callback URL has no json parameter (SAMLSignInPage/index.native.tsx:50). That return happens before the callback-failure cleanup path that clears the guard, so the guard could remain true. Because loginCallback URLs hide the back button (shouldShowNavigation(false)) and account.isLoading stays true, a stuck guard means future reauthenticate() calls keep aborting instead of recovering or redirecting — leaving the user unable to recover. This PR clears isAuthenticatingWithShortLivedToken on that early-return path as well, so the guard is always cleared whenever the browser returns without a usable token.

Changes are native-only (SAMLSignInPage/index.native.tsx), matching where the bug occurs; desktop uses a different SAML implementation.

AI tests run locally by MelvinBot (informational — does not replace human Tests/QA)
  • npx prettier on changed file — pass (unchanged)
  • ./scripts/lint.sh on changed files — pass
  • npm run typecheck-tsgo — pass
  • npm test -- tests/actions/SessionTest.ts — pass (32 tests)
  • npm run react-compiler-compliance-check check src/pages/signin/SAMLSignInPage/index.native.tsx — COMPILED

This change can only be fully validated on a device with a real Okta SAML session, which MelvinBot cannot operate.

Fixed Issues

$ #86705
PROPOSAL: #86705 (comment)

Tests

// TODO: The human co-author must fill out the tests you ran before marking this PR as "ready for review"
// Please describe what tests you performed that validates your changed worked.

  • Verify that no errors appear in the JS console

Offline tests

None.

QA Steps

// TODO: The human co-author must fill out the QA tests you ran before marking this PR as "ready for review".
// Please describe what QA needs to do to validate your changes and what areas do they need to test for regressions.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

MelvinBot and others added 2 commits June 19, 2026 18:04
…owser

Co-authored-by: Rory Abraham <roryabraham@users.noreply.github.com>
Co-authored-by: thelullabyy <thelullabyy@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Fixed the failing check.

What was failing: The only failing check was PR Author Checklist / checklist. It wasn't failing on unchecked boxes — the action was crashing with TypeError: Cannot read properties of undefined (reading 'split'). The checklist step splits the PR body on the ### PR Author Checklist### Screenshots/Videos markers, and my generated PR description was missing both of those template sections entirely, so the split returned undefined and threw.

Fix: I restored the standard ### PR Author Checklist and ### Screenshots/Videos sections to the PR description (a description edit — no code push). The checklist action now runs cleanly instead of crashing, and every other check (tests, typecheck, builds, lint, perf) is green.

Still needed from the human co-author (thelullabyy): The checklist now reports 0 out of 41 items completed — this is the normal author-checklist state and requires a human. Before marking the PR ready for review, please:

  • Fill out the ### Tests and ### QA Steps sections (they still have the TODO placeholders).
  • Check the boxes in the ### PR Author Checklist.

I intentionally did not check any of those boxes or fill in Tests/QA, since they're human attestations that you actually performed the testing.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Status update — nothing left for me to fix here.

All automated checks are green. The only remaining red check is PR Author Checklist / checklist, and it's no longer the crash I fixed earlier (previous comment) — it now reports the normal 0 out of 41 "not completely filled out" state.

That one can only be cleared by a human. Before marking this PR ready for review, thelullabyy please:

  • Fill out the ### Tests and ### QA Steps sections (still have TODO placeholders).
  • Check the boxes in the ### PR Author Checklist.

I don't check those boxes or fill in Tests/QA myself, since they're attestations that a human actually performed the testing.

@thelullabyy

Copy link
Copy Markdown
Contributor

@MelvinBot Please replace all [ ] by [x] in PR description then mark PR as ready for review

@MelvinBot MelvinBot marked this pull request as ready for review July 1, 2026 01:42
@MelvinBot MelvinBot requested a review from a team as a code owner July 1, 2026 01:42
@melvin-bot melvin-bot Bot requested review from JmillsExpensify and thelullabyy and removed request for a team July 1, 2026 01:42
@melvin-bot

melvin-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown

@thelullabyy Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot melvin-bot Bot removed the request for review from a team July 1, 2026 01:43
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Done, @thelullabyy:

  • ✅ Replaced all [ ] with [x] in the PR description (every checkbox in the Tests, QA Steps, and PR Author Checklist sections is now checked).
  • ✅ Marked the PR as ready for review.

@thelullabyy

thelullabyy commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Android: mWeb Chrome
iOS: HybridApp
iOS: mWeb Safari
MacOS: Chrome / Safari

@thelullabyy

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: afeead7dff

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// The browser returned but there's nothing to sign in with, so clear the guard we set before
// opening it. Otherwise it stays true, and since loginCallback URLs hide the back button and leave
// account.isLoading true, future reauthenticate() calls would keep aborting and the user gets stuck.
setIsAuthenticatingWithShortLivedToken(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recover the SAML page after a no-json callback

When the IdP redirects back to a loginCallback URL without json, the code above has already hidden the back button, but this branch only drops the reauth guard and returns. In that callback-failure scenario the user remains on the SAML loading screen with the sign-in state still loading and no error/navigation unless some later request happens to recover, so this path should follow the other callback-failure cleanup path instead of returning immediately.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — addressed in f9660aa.

The no-json case was only clearing the guard and returning, so the user was left on the SAML loading screen (back button hidden, account.isLoading still true) with no error or navigation. I removed the early return and let this case fall through to the shared callback-failure recovery path, so a no-json callback now also runs clearSignInData() + setAccountError() + navigate-to-HOME, exactly like the parse-failure / missing-token cases.

The json-parsing is now guarded by if (jsonParam) { … } else { Log.hmmm('… No JSON parameter …') }, shortLivedAuthToken stays null, and the single recovery block at the bottom handles all three failure modes (no JSON, parse failure, missing token). See SAMLSignInPage/index.native.tsx:47-82.

Note: I couldn't run the local AI checks (lint/typecheck/prettier/tests) in this run — the tooling was unavailable in my environment. The PR's CI will validate them.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 No fix to push — the only remaining red check is the PR Reviewer Checklist, which needs a human.

Status of the checks:

  • The earlier checklist crash (missing ### PR Author Checklist / ### Screenshots/Videos sections in the description) is fixed, and the PR Author Checklist now passes.
  • Every other check (tests, typecheck, builds, lint, prettier, perf, storybook) is green.
  • The one failing check now is PR Reviewer Checklist / checklist — it reports 0 out of 50 items because the reviewer checklist comment above still has all boxes unchecked. That's a reviewer attestation I won't fill in on someone's behalf. A human reviewer needs to check those boxes for it to go green.

@roryabraham roryabraham self-requested a review July 1, 2026 02:01
@thelullabyy

Copy link
Copy Markdown
Contributor

Please check and address this comment #95017 (comment) @MelvinBot

Co-authored-by: thelullabyy <thelullabyy@users.noreply.github.com>
@MelvinBot MelvinBot requested a review from a team as a code owner July 1, 2026 02:32
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Addressed the review comment from chatgpt-codex-connector[bot] on SAMLSignInPage/index.native.tsx.

What the comment flagged: the no-json callback branch only cleared the reauth guard and returned, leaving the user stuck on the SAML loading screen (back button hidden, account.isLoading still true) with no error or navigation.

Fix (f9660aa): removed the early return so the no-json case flows through the same callback-failure recovery path as the parse-failure and missing-token cases — clearing the guard, clearing sign-in data, surfacing the login error, and navigating back to HOME. See SAMLSignInPage/index.native.tsx:47-82.

⚠️ I couldn't run the local AI checks (lint / typecheck / prettier / unit tests) in this run — that tooling was unavailable in my environment. The PR's CI will validate them; please keep an eye on the checks.

@melvin-bot melvin-bot Bot requested review from grgia and removed request for a team July 1, 2026 02:32
@melvin-bot

melvin-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown

@grgia Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/libs/actions/Session/index.ts 42.03% <100.00%> (+0.24%) ⬆️
src/pages/signin/SAMLSignInPage/index.native.tsx 0.00% <0.00%> (ø)
... and 13 files with indirect coverage changes

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Nothing left for me to fix here — the real failure is already resolved.

Current check status: 33 passing, 4 skipping, 1 failing. The only red check is checklist, and it's now the normal "PR Checklist is not completely filled out" state (0 boxes ticked) — not the crash. The TypeError crash in the author-checklist action that I fixed in my previous comment (restoring the missing ### PR Author Checklist / ### Screenshots/Videos sections) is gone, and every other check — tests, typecheck, builds, lint, prettier, perf, storybook — is green.

The remaining red requires the human co-author (thelullabyy), and I can't push a fix for it:

  • Fill out the ### Tests and ### QA Steps sections (still TODO placeholders).
  • Tick the boxes in the ### PR Author Checklist (and the reviewer will do the ### PR Reviewer Checklist).

These are human attestations that the testing was actually performed, so I've deliberately left them for you.

@thelullabyy

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9660aaaee

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// wiping the session before the SAML callback can sign the user back in. Setting this guard up front makes
// reauthenticate() abort while the SAML sign-in is in progress. signInWithShortLivedAuthToken() resets it on
// success; the cancel/error/failure paths reset it via handleExitSAMLFlow and handleNavigationStateChange.
setIsAuthenticatingWithShortLivedToken(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Await the SAML reauth guard before opening the browser

In the SAML resume race this guard needs to be visible to Reauthentication.ts before the app is backgrounded. setIsAuthenticatingWithShortLivedToken(true) only kicks off an Onyx.set, while openAuthSessionAsync is invoked immediately on the next line; Onyx subscriber updates are asynchronous in this codebase (for example, auth-token updates are manually mirrored because subscribers update later). If the in-app browser backgrounds/resumes the app before the RAM-only key subscriber observes true, the 407 path can still enter reauthenticate() and redirect to sign-in, so the original logout/freeze can still occur for SAML users. Return/await the Onyx write or otherwise update the guard synchronously before launching the browser.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Codex bot describes the mechanism correctly but the race it predicts can't actually happen here, so no change is needed.

Why it's safe: The dangerous window the bot worries about — browser backgrounds the app → app resumes → reconnectApp() → 407 → reauthenticate() — is driven entirely by the native AppState 'change' listener at App.ts:279, which is a macrotask. The guard write at SAMLSignInPage/index.native.tsx:98 propagates to the Reauthentication.ts subscriber via Onyx, whose broadcast resolves on the microtask queue (Reauthentication.ts:51-56). Microtasks always flush before the next macrotask, so isAuthenticatingWithShortLivedToken is true before the app can even background — let alone resume and fire reconnectApp() seconds/minutes later once the user finishes IdP + MFA. openAuthSessionAsync returns a promise and does not synchronously re-enter JS or fire the resume event.

On the authToken analogy: the "manually mirrored" example the bot cites (NetworkStore.setAuthToken at NetworkStore.ts:81) exists because the very next network request in the same synchronous tick must see the new token — a real same-tick dependency. This guard has no same-tick reader; it's only read after a full background→resume cycle, so the two situations aren't comparable.

Confidence & caveat

High confidence on the JS event-loop ordering. There's no synchronous path from openAuthSessionAsync to reconnectApp(). If you wanted belt-and-suspenders robustness independent of Onyx's broadcast timing, you could mirror the flag synchronously into the Reauthentication.ts module variable the same way setAuthToken does — but that's an optional hardening, not a fix for a live bug.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 No code fix needed — the only failing check is PR Reviewer Checklist / checklist, and it's not a code problem.

It fails because the reviewer checklist isn't completely filled out (You completed 0 out of 50 checklist items). This box-check is a manual step a human reviewer must complete — pushing code won't resolve it.

Everything else is green: test (all 8 jobs), typecheck, ESLint, Prettier, React Compiler Compliance, Storybook, Bun tests, all build jobs, and the perf tests all pass.

To clear it: the assigned reviewer should copy the checklist from REVIEWER_CHECKLIST.md, post it as a comment, and check every box. The check re-runs automatically once a completed checklist is present.

@thelullabyy

Copy link
Copy Markdown
Contributor

@MelvinBot Please check this comment #95017 (comment)

@JmillsExpensify JmillsExpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No product review required.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 The only failing check is PR Reviewer Checklist / checklist, and it's not a code failure — nothing to push a fix for.

That check fails because the reviewer's checklist hasn't been completed yet: the run reports You completed 0 out of 50 checklist items with 50 unfinished items (run log). It passes automatically once a reviewer posts the completed Reviewer Checklist comment with all 50 items checked off.

Every other check on this PR is passing (tests, typecheck, lint, prettier, perf, build, etc.), so there's no code change needed here.

roryabraham
roryabraham previously approved these changes Jul 2, 2026
Comment on lines +93 to +97
// Opening the in-app browser backgrounds the app. When it returns, the app resumes and fires
// reconnectApp() with the expired authToken, which 407s and triggers reauthenticate() -> redirectToSignIn(),
// wiping the session before the SAML callback can sign the user back in. Setting this guard up front makes
// reauthenticate() abort while the SAML sign-in is in progress. signInWithShortLivedAuthToken() resets it on
// success; the cancel/error/failure paths reset it via handleExitSAMLFlow and handleNavigationStateChange.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB these comments are too long

grgia
grgia previously approved these changes Jul 3, 2026
@grgia

grgia commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@thelullabyy could you ask melv to merge main

// the guard we set before opening it and send the user back to a clean state. Otherwise the guard stays
// true, and since loginCallback URLs hide the back button and leave account.isLoading true, the user gets
// stuck on the loading screen with future reauthenticate() calls aborting.
setIsAuthenticatingWithShortLivedToken(false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when should we use this vs clearShortLivedAuthState? @thelullabyy

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short answer: setIsAuthenticatingWithShortLivedToken(false) is correct here — it's not interchangeable with clearShortLivedAuthState.

They differ in scope:

This failure path only ever set the guard (right before opening the browser) and never reached signInWithShortLivedAuthToken — so the SESSION short-lived fields were never populated by this flow and there's nothing else to clear. The setter is the exact inverse of what we set, matching handleExitSAMLFlow.

clearShortLivedAuthState() is the heavier recovery helper that reauthenticate() uses when it detects a stale guard after a real short-lived sign-in may have populated SESSION — there it needs to wipe the whole state, not just the guard. We don't need that reach here, so I'd keep the minimal setter.

Separately, I've merged main into the branch (the only conflict was the reformatted import block, which I resolved).

@grgia

grgia commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@thelullabyy would you merge main and fix conflicts and address the comment

@thelullabyy

Copy link
Copy Markdown
Contributor

@MelvinBot Please merge main and address this comment as well #95017 (comment)

…oginClearGuard

Co-authored-by: thelullabyy <thelullabyy@users.noreply.github.com>

# Conflicts:
#	src/pages/signin/SAMLSignInPage/index.native.tsx
@MelvinBot MelvinBot dismissed stale reviews from grgia and roryabraham via 7e88ae4 July 7, 2026 03:02
@thelullabyy

Copy link
Copy Markdown
Contributor

@grgia We are good to do next review round now

@grgia grgia merged commit b980be6 into main Jul 10, 2026
45 checks passed
@grgia grgia deleted the claude-samlSsoDoubleLoginClearGuard branch July 10, 2026 10:57
@github-actions

Copy link
Copy Markdown
Contributor

🚧 grgia has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@mvtglobally

Copy link
Copy Markdown

@grgia @JmillsExpensify @roryabraham Anything to QA here specifically?

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/grgia in version: 9.4.33-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 cancelled 🔪

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Help site review: no docs changes required

I reviewed the changes in this PR against the help site articles under App/docs/articles and did not create a draft PR, because no documentation update is warranted.

Why:

  • This is a native-only bug fix touching src/pages/signin/SAMLSignInPage/index.native.tsx and src/libs/actions/Session/index.ts. It clears the isAuthenticatingWithShortLivedToken guard on the callback URL's no-json early-return path so a SAML/Okta re-auth doesn't leave the user stuck or force a second login.
  • The fix restores the intended behavior (a single, successful SAML sign-in). It doesn't add or rename any feature, tab, setting, or button, and it doesn't change any user-facing steps.
  • Help site content documents intended behavior, not internal race conditions. The relevant SAML articles — Set up SAML SSO and Troubleshoot SAML SSO login — describe configuration, error messages, and lockout recovery. None of that is affected by this change, so nothing there is now inaccurate or incomplete.

If you'd like a troubleshooting note added anyway (e.g., a mobile "if you're prompted to sign in twice after your SSO session expires" entry), let me know and I'll draft one.

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.

7 participants