Skip to content

fix(apollo-vertex)!: key the shell session by client and scope - #1369

Merged
RobinMennens-UiPath merged 2 commits into
mainfrom
fix/apollo-vertex-auth-scope-drift
Oct 15, 2026
Merged

RobinMennens-UiPath merged 2 commits into
mainfrom
fix/apollo-vertex-auth-scope-drift

Conversation

@RobinMennens-UiPath

@RobinMennens-UiPath RobinMennens-UiPath commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Problem

There are two problems, and they share a cause: the shell keeps its session under one fixed key, uipath_token.

1. A scope change never reaches a signed-in user. ShellAuthProvider keeps a session going with refresh tokens. A refresh-token grant returns the scopes of the original login, so a scope added later never reaches an existing session. The token lacks that API's audience, and the API answers 401. We hit this in a customer UAT: LLMOps returned IDX10214: Audience validation failed on every call to the feedback service, until each user signed out and in.

2. Apps on the same host overwrite each other's session. Coded apps in one org are all served from <org>.uipath.host/<app>, so they share one localStorage, and every one of them reads and writes uipath_token. When the apps use different OAuth clients (for example one external application per solution), whichever app signed in last wins. The other app then refreshes a token issued to a different client, which fails.

Why key by client and scope

The key is uipath_token:<clientId>:<scopes, de-duplicated and sorted>. Each part solves one problem:

  • Client separates apps that use different OAuth clients. Apps that share a client also share their scopes on UiPath Apps, because the host fills uipath:scope from the client's grants when it deploys the app. So they get the same key and share one session, which is correct since the token suits both.
  • Scope makes a scope change force a new login. The new key has no session, so the user signs in and gets the new scopes, where a refresh never could. No comparison against the stored session is needed.
Situation Result
Two apps, different clients Separate sessions, no collision
Two apps, same client (so same scopes on UiPath Apps) One shared session
The client's scopes change, and the app is redeployed so uipath:scope picks them up New key, so the user signs in again and gets the new scopes

Keying by the app's path (e.g. /loansetupapp) would also isolate apps, but it has three costs:

  • The registry would need the path passed in, as a new prop or a UiPath Apps meta tag.
  • It still needs a scope comparison to catch scope changes.
  • Apps sharing a client would sign in separately.

Client plus scope needs no new input and covers both problems.

Change

  • lib/auth.ts:

    • The token storage key is derived from clientId and the normalized scope, and refresh writes back to the same key.
    • getTokenQueryKey(clientId, scope) gives the React Query cache entry a matching key. A caller using another client or scope gets its own entry, instead of sharing one with the shell and flipping it between signed in and signed out. A scope change also takes effect at once rather than at the next 60-second refetch. Sign-out resets only the client's entries, so other apps sharing a QueryClient keep their cached tokens.
    • getStoredIdToken(clientId, scope) reads the id token from this app's session, for code that needs it outside the shell. The AI Chat template's logout uses it to build id_token_hint.
  • scope is required wherever the session is read, and must be the same scope given to login. ShellAuthProvider already requires it, so apps that only use the provider need no change. An optional scope would need a fallback key, and a caller that omitted it could handle the login callback and store the token where the shell never looks. With scope required, a missed call site fails to compile.

  • The login callback:

    • Clears the single-use ?code and the PKCE state on every exit, including an error returned by Identity (?error=…&state=…), a state mismatch, or a missing verifier. Otherwise each token refetch would treat the page as a fresh callback and fail again. It returns to the page the login started from.
    • Checks that the login used the current scope (the scope login recorded in sessionStorage) before redeeming the code. If not, for example a login started from a bundle deployed before this one, it skips the exchange and asks the user to sign in again.
    • Doesn't reset the token query when it fails. The callback runs inside that query's fetch, so a reset started a second run beside it. With a session close to expiry, both runs refreshed with the same refresh token. After a failure the query just goes on to read the stored session.
  • logout ends every session of the client: the current key, keys from earlier scopes, and the unscoped pre-upgrade entry, all of which hold refresh tokens. The unscoped entry is shared by every app on the host, so it is removed only when its id token's audience is this client.

  • lib/auth.test.ts covers:

    • Session lookup: scope order and spacing, a scope change, two clients on one host, ignoring the unscoped key, refresh, and a failed refresh.
    • The query key, and that a failed callback refreshes a stored session only once.
    • The login callback: success, a login under another scope, a login with no recorded scope, an error from Identity, a state mismatch, and a failed exchange.
    • getStoredIdToken, and logout across sessions, cached tokens and clients, including an unscoped entry owned by another client.

    The vitest include covers lib/**/*.test.ts.

  • packages/apollo-vertex/AGENTS.md drops its note that the package has no tests.

Breaking change

This is a major bump of @uipath/apollo-vertex (0.3.0 to 1.0.0). The commit fix(apollo-vertex)!: key the shell session by client and scope carries a BREAKING CHANGE: footer, because it changes the required-ness and order of public parameters:

Export Before After
ensureValidToken (queryClient, clientId, baseUrl, redirectPath?) (clientId, scope, baseUrl, redirectPath?), mirroring login; no queryClient
useAccessToken { clientId, baseUrl, redirectPath? } adds required scope
logout (queryClient) (queryClient, clientId)
TOKEN_QUERY_KEY the token's cache key only the prefix; read the cache with getTokenQueryKey(clientId, scope)
getTokenQueryKey new (clientId, scope)
getStoredIdToken new (clientId, scope)

Migration: pass the same scope you give login, and drop the queryClient argument of ensureValidToken. A getQueryData(TOKEN_QUERY_KEY) or fetchQuery({ queryKey: TOKEN_QUERY_KEY }) moves to getTokenQueryKey(clientId, scope), and a direct read of localStorage[STORAGE_KEYS.TOKEN] moves to getStoredIdToken. Apps that only use ShellAuthProvider and useAuth() are unaffected.

Rollout

  • One sign-in after the upgrade. An app that picks this up looks under the new key, so every user signs in once. That's deliberate: it also clears any session that's already stale. With the Identity SSO session still active, it's usually one click.
  • Old entries stay until sign-out. Neither the unscoped uipath_token entry nor a previous scope's key is removed on upgrade or on a scope change, because a sibling app on the same host may still own it. Signing out removes all of them that belong to that client.

FINS (fins-vertical-solution) picks this up at its next registry sync. Its getSdkToken and the services that read the token from the query cache need the new key.

Checks

  • pnpm test: 22 passed (2 files)
  • pnpm typecheck, pnpm lint (type-aware), pnpm format:check: clean
  • pnpm lint:deps: 0 errors (the 11 warnings are already on main)
  • pnpm -w knip: clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01F2x5QWqQBmo2vNFJF3Xtbq

Copilot AI balanced review requested due to automatic review settings October 14, 2026 10:02
@RobinMennens-UiPath
RobinMennens-UiPath requested a review from a team as a code owner October 14, 2026 10:02
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Apollo Coded App preview deployments are ready.

Project Status Preview Updated (PT)
apollo-design Ready Preview · Logs Oct 15, 2026, 06:00:30 AM
apollo-docs Ready Preview · Logs Oct 15, 2026, 06:00:30 AM
apollo-landing Ready Preview · Logs Oct 15, 2026, 06:00:30 AM
apollo-vertex Ready Preview · Logs Oct 15, 2026, 06:00:30 AM

@github-actions

Copy link
Copy Markdown
Contributor

Dependency License Review

  • ✅ 1942 package(s) scanned
  • ✅ No license issues found
  • ⚠️ 2 package(s) excluded (see details below)
License distribution
License Packages
MIT 1708
ISC 91
Apache-2.0 56
BSD-3-Clause 28
BSD-2-Clause 23
BlueOak-1.0.0 8
MPL-2.0 4
MIT-0 3
CC0-1.0 3
MIT OR Apache-2.0 2
(MIT OR Apache-2.0) 2
Unlicense 2
LGPL-3.0-or-later 1
Python-2.0 1
CC-BY-4.0 1
(MPL-2.0 OR Apache-2.0) 1
Unknown 1
Artistic-2.0 1
(WTFPL OR MIT) 1
(BSD-2-Clause OR MIT OR Apache-2.0) 1
CC-BY-3.0 1
0BSD 1
(MIT OR CC0-1.0) 1
MIT AND ISC 1
Excluded packages
Package Version License Reason
@img/sharp-libvips-linux-x64 1.3.4 LGPL-3.0-or-later LGPL pre-built binary, not linked
khroma 2.1.0 Unknown MIT per GitHub repo, missing license field in package.json

Copilot AI 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.

🟡 Changes recommended

The query cache does not immediately revalidate when scope changes, and the login-side scope persistence lacks direct coverage.

2 open findings
What changed in this PR

Adds scope-aware OAuth session invalidation to Apollo Vertex authentication.

Changes:

  • Persists requested scopes across login and refresh flows.
  • Invalidates tokens when configured scopes differ.
  • Adds authentication tests, Vitest coverage, and documentation.
File Description
packages/​apollo-vertex/​vitest.config.ts Includes authentication unit tests.
packages/​apollo-vertex/​registry/​shell/​shell-auth-provider.tsx Passes configured scopes into token validation.
packages/​apollo-vertex/​lib/​auth.ts Stores, compares, and preserves requested scopes.
packages/​apollo-vertex/​lib/​auth.test.ts Tests scope validation and persistence.
apps/​apollo-vertex/​content/​docs/​patterns/​shell.mdx Documents reauthentication after scope changes.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/apollo-vertex/registry/shell/shell-auth-provider.tsx Outdated
Comment thread packages/apollo-vertex/lib/auth.test.ts Outdated
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

📊 Coverage + size by package

Per-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.

Package Coverage New-line coverage Packed (gzip) Unpacked vs main
@uipath/apollo-core — — — — —
@uipath/apollo-react — — — — —
@uipath/apollo-ui-icons — — — — —
@uipath/apollo-vertex 84.0% 90.5% (57/63) 325.5 KB 1.84 MB +834 B
@uipath/apollo-wind — — — — —
@uipath/ap-chat — — — — —

"Coverage" is each package's own coverage.include scope (e.g. apollo-core instruments only scripts/). "Packed"/"Unpacked" come from npm pack --dry-run and only cover built packages — "—" means not measured this run (package not affected / not built). "vs main" is the packed (gzipped) delta against the last successful main build (the package-sizes artifact from the Release workflow); "—" there means no main baseline was available this run. The baseline is main's latest build, not this PR's exact merge-base, so it includes any drift since the branch diverged. Packages with no vitest config are omitted.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Storybook visual diff

✅ No stories are affected by this PR's changes; nothing to compare. Logs

Updated (PT): Oct 15, 2026, 06:01:30 AM

Copilot AI balanced review requested due to automatic review settings October 14, 2026 10:12
@RobinMennens-UiPath
RobinMennens-UiPath force-pushed the fix/apollo-vertex-auth-scope-drift branch from 7a20b43 to a5aed0e Compare October 14, 2026 10:12

Copilot AI 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.

🟢 Approval recommended

The implementation matches the described behavior and includes focused coverage for the new authentication flow.

0 open findings

2 resolved since last review

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 14, 2026 10:54
@RobinMennens-UiPath RobinMennens-UiPath changed the title fix(apollo-vertex): sign in again when the shell auth scope changes fix(apollo-vertex): key the shell session by client and scope Oct 14, 2026

Copilot AI 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.

🟡 Changes recommended

The new scoped key breaks the AI Chat logout flow’s ID-token lookup and may reuse a stale legacy token.

1 open finding

🧠 Review effort: Balanced

Comment thread packages/apollo-vertex/lib/auth.ts Outdated
Copilot AI balanced review requested due to automatic review settings October 14, 2026 10:59

Copilot AI 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.

🟢 Approval recommended

The implementation and tests address the session collision safely; only a minor documentation clarification remains.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Low severity Clarify key sharing across apps with differing normalized scopes

apps/​apollo-vertex/​content/​docs/​patterns/​shell.mdx:95

This overstates the isolation: apps with the same client and normalized scopes intentionally share one key. Clarify the differing-scope case so consumers do not assume sessions are isolated per app path.

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 14, 2026 11:07

Copilot AI 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.

🟢 Approval recommended

The implementation consistently applies scoped storage, preserves legacy behavior, and includes focused regression coverage.

0 open findings

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 14, 2026 11:19
@RobinMennens-UiPath RobinMennens-UiPath changed the title fix(apollo-vertex): key the shell session by client and scope fix(apollo-vertex)!: key the shell session by client and scope Oct 14, 2026
@RobinMennens-UiPath RobinMennens-UiPath self-assigned this Oct 14, 2026

Copilot AI 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.

🔵 Needs a closer look

The core isolation behavior for different OAuth clients lacks regression coverage.

0 open findings

Previously missed (1)

In code that hasn't changed since last review

Medium severity Add test coverage for client ID partitioning

packages/​apollo-vertex/​lib/​auth.test.ts:101

The suite verifies scope partitioning but never client partitioning, which is the second core regression this change is intended to prevent. If clientId were accidentally removed from the storage key, every test here would still pass while apps using different OAuth clients would overwrite each other again. Add a test using the same scope with two client IDs and assert that each retrieves its own token.

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 14, 2026 11:50
@RobinMennens-UiPath
RobinMennens-UiPath force-pushed the fix/apollo-vertex-auth-scope-drift branch from 5859fee to 6df1f15 Compare October 14, 2026 11:50

Copilot AI 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.

🟡 Changes recommended

OAuth error callbacks remain uncleared, and logout can delete another client’s legacy session.

2 open findings

🧠 Review effort: Balanced

Comment thread packages/apollo-vertex/lib/auth.ts Outdated
Comment thread packages/apollo-vertex/lib/auth.ts Outdated
Copilot AI balanced review requested due to automatic review settings October 14, 2026 13:12
@RobinMennens-UiPath
RobinMennens-UiPath force-pushed the fix/apollo-vertex-auth-scope-drift branch from 6df1f15 to 75c10a9 Compare October 14, 2026 13:12

Copilot AI 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.

🟡 Changes recommended

Query resets still affect unrelated client and scope entries, undermining the newly introduced cache isolation.

1 open finding
2 resolved since last review

🧠 Review effort: Balanced

Comment thread packages/apollo-vertex/lib/auth.ts Outdated
Copilot AI balanced review requested due to automatic review settings October 15, 2026 08:33
@RobinMennens-UiPath
RobinMennens-UiPath force-pushed the fix/apollo-vertex-auth-scope-drift branch from 75c10a9 to 0b9b14d Compare October 15, 2026 08:33
@github-actions github-actions Bot added size:XL 500-999 changed lines. and removed size:L 100-499 changed lines. labels Oct 15, 2026

Copilot AI 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.

🟡 Changes recommended

An unrelated token reader can consume and clear another client's OAuth callback, preventing sign-in.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread packages/apollo-vertex/lib/auth.ts Outdated
Comment thread packages/apollo-vertex/registry/shell/shell-auth-provider.tsx Outdated
Comment thread packages/apollo-vertex/AGENTS.md Outdated
Comment thread apps/apollo-vertex/content/docs/patterns/shell.mdx Outdated
Comment thread packages/apollo-vertex/lib/auth.ts Outdated
Comment thread packages/apollo-vertex/lib/auth.ts
Comment thread packages/apollo-vertex/lib/auth.ts Outdated
Copilot AI balanced review requested due to automatic review settings October 15, 2026 12:20
@RobinMennens-UiPath
RobinMennens-UiPath force-pushed the fix/apollo-vertex-auth-scope-drift branch from 0b9b14d to 8f7f61d Compare October 15, 2026 12:20

Copilot AI 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.

🔵 Needs a closer look

The breaking authentication API and session migration need maintainer validation of downstream integrations before release.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

RobinMennens-UiPath and others added 2 commits October 15, 2026 14:55
Coded apps in one org share a host, so they share one localStorage, and
the shell kept every app's session under the same uipath_token key. Apps on
different OAuth clients overwrote each other's session. And a refresh-token
grant keeps the scopes of the original login, so a scope added to the
client later never reached a signed-in user: APIs that need it answered
401 (IDX10214 from LLMOps) until the user signed out and in.

The session now lives under uipath_token:<clientId>:<sorted scopes>, and
its React Query cache entry under a matching key. Apps on different clients
keep separate sessions, and a scope change lands on a new key, so the user
signs in again and gets the new scopes.

The login callback clears the single-use code and PKCE state on every exit,
including an error returned by Identity, so a failed callback no longer
retries on each token refetch. A login that
began under another scope is not redeemed, and the user is asked to sign
in again. Signing out ends every session of the client, and the unscoped
pre-upgrade entry only when its id token's audience is this client, and
resets only that client's cached tokens, so other apps on a shared
QueryClient keep theirs. A failed callback no longer resets the token query
from inside its own fetch, which started a second run that could refresh
the same refresh token twice.

BREAKING CHANGE: `scope` is required wherever the shell session is read.
`ensureValidToken` takes `(clientId, scope, baseUrl, redirectPath?)`,
mirroring `login`, and no longer takes a `queryClient`.
`useAccessToken` requires `scope`, and `logout` requires `clientId`. Read
the token cache with `getTokenQueryKey(clientId, scope)` instead of
`TOKEN_QUERY_KEY`, which is now only the prefix of those keys, and the
id token with `getStoredIdToken(clientId, scope)` instead of reading
`STORAGE_KEYS.TOKEN`. Pass the same scope you give `login`. Sessions under
the old key are not read, so users sign in once after upgrading.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F2x5QWqQBmo2vNFJF3Xtbq
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F2x5QWqQBmo2vNFJF3Xtbq
Copilot AI balanced review requested due to automatic review settings October 15, 2026 12:55
@RobinMennens-UiPath
RobinMennens-UiPath force-pushed the fix/apollo-vertex-auth-scope-drift branch from 8f7f61d to 0ccc936 Compare October 15, 2026 12:55

Copilot AI 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.

🔵 Needs a closer look

The breaking authentication API, downstream cache-key migration, and mixed-version rollout require maintainer integration validation.

0 open findings

🧠 Review effort: Balanced

@RobinMennens-UiPath
RobinMennens-UiPath merged commit 65d5d9e into main Oct 15, 2026
46 of 48 checks passed
@RobinMennens-UiPath
RobinMennens-UiPath deleted the fix/apollo-vertex-auth-scope-drift branch October 15, 2026 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app:apollo-vertex size:XL 500-999 changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants