fix(ui): guard SSO redirect to stop 401 retry loop - #28807
Conversation
Signed-off-by: Eric Hole <eric.hole@gmail.com>
❗ Preview Environment deployment failed on BunnyshellSee: Environment Details | Pipeline Logs Available commands (reply to this comment):
|
Bundle ReportChanges will increase total bundle size by 132.44kB (1.19%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: argo-cd-ui-array-pushAssets Changed:
|
dudinea
left a comment
There was a problem hiding this comment.
@geojaz Thank you for your PR,
After some hard trying I've succeeded to reproduce the issue,
like @trv-bmpage in #28649 I used OIDC against Okta with custom auth server, enablePKCEAuthentication: true, offline_access + refreshTokenThreshold.
Your PR seems to fix the problem, with it I still see bunch of 401 errors from argocd-server when the token expires, but this no more causes the "Too many requests" error from Okta.
I still see two problems with the handling of timeout:
- There is a chance that the flag might get stuck: is for any reason the redirect fails (some network failure or timeout) the flag will stay true and the page won't do redirect until user reloads the page.
- While reload happens (and that may take some time), the API server continues to be severly hammered by UI requests which continuously get error 401 and then immediately retry the same operation. Probably once the redirect started all the other requests should be cancelled and only redirect should be retried. Or generic backoff mechanism should be implemented.
I see the former as a bigger, user-visible problem that can be easily fixed in this PR, something like setTimeout(()-> {ssoRedirectInProgress = false}, 5000) when setting the ssoRedirectInProgress to true.
The second one probably is a generic problem, not specifically related to SSO and should be dealt with as a separate issue.
Addresses review feedback on argoproj#28807: if the redirect navigation fails, the guard flag would stay true and block any future SSO redirect until a manual page reload. Reset it after 5s so a later 401 can retry.
Addresses review feedback on argoproj#28807: if the redirect navigation fails, the guard flag would stay true and block any future SSO redirect until a manual page reload. Reset it after 5s so a later 401 can retry. Signed-off-by: Eric Hole <ehole@onixnet.com>
0d26484 to
fddea9f
Compare
|
Thanks for taking the time to reproduce this.
|
Fixes #24807
When an OIDC session expires, each open watch stream emits a 401 into the shared
requests.onErrorsubject.subscribeUnauthorizedreassignswindow.location.hrefon every one of them, so each 401 cancels the previous in-flight navigation to the identity provider. The browser never finishes leaving the page, and the streams keep reconnecting and re-emitting 401s.This adds a module-level flag so only the first 401 triggers the redirect.
The check and the assignment are deliberately adjacent, immediately before
window.location.href.await isExpiredSSO()earlier in the handler yields the event loop, so a guard placed at the top of the function would let several handlers past before any of them set the flag, which would leave the race intact.No explicit reset is needed. The redirect is a full page load, so the module state is discarded on the way back in.
Only the SSO branch is guarded. The local-account branch uses
history.pushand is already covered by the existing pathname checks.What I saw
I hit this myself on v3.4.4. A tab I had left open on an application view lost its session at 00:43 UTC. At 01:16 the streams started retrying and did not stop until I completed a fresh login at 01:30, with the last failure logged at 01:36. Over that roughly 20 minute window argocd-server logged 26,522
Failed to verify session tokenwarnings and the load balancer in front of it logged 38,649 requests to the argocd-server backend. That works out to somewhere between 1,300 and 1,900 requests per minute from a single browser tab.Every one of those warnings carried the same token expiry timestamp, which is what pointed at the redirect race rather than at ordinary session churn.
My setup is dex with the Google connector, behind GCP IAP and GKE ingress. The earlier reports on the issue were Okta, so this looks identity-provider agnostic, which matches the code path.
Not included
watch()inapplications-service.tsstill uses argument-less.pipe(repeat()).pipe(retry()), so the streams keep reconnecting without backoff until login completes. That is a separate concern and #24807 discusses it as a follow-up.Checklist
pnpm lintpasses (tsc --noEmit --project ./src/app && eslint), 0 errors.app.tsxwhich would require setting up mocks for a root React component and an RxJs subject... but if you really want a test just LMK.