You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@girder/components@4.0.0 (and likely earlier v4 releases) has three interacting issues in src/utils/restClient.js that, together, prevent reload-survives-auth when the Girder backend sets girderToken as HttpOnly (the default since girder/girder#3484, Dec 2023) and the front-end is served from a different origin than Girder (very common in dev: Vite/webpack on :5173/:8080 → Girder on a different port).
1. setCookieFromHash returns the entire URL hash as a "token" when no marker is present
restClient.js:19-29:
functionsetCookieFromHash(location){constarr=location.hash.split(OauthTokenPrefix)consttoken=arr[arr.length-1].split(OauthTokenSuffix)[0]if(token.length===GirderTokenLength){// ... set cookie ...}returntoken// <-- returned even when no marker matched}
When window.location.hash is e.g. #/some/route/123 (i.e., any hash-routed SPA), the function returns "/some/route/123" as a "token". That value is then stored in this.token and sent as Girder-Token: /some/route/123 on every request → 401.
Pre-#3484, this code was dead in practice because cookies.get('girderToken') always succeeded first. Post-#3484, the JS cookie is gone, so this fallback runs and trips on the SPA's route hash.
Fix: only return the parsed token if it matched the marker; otherwise return null/empty.
2. The request interceptor sends Girder-Token even when empty, suppressing cookie auth
Girder's auth precedence is ?token= → Girder-Token header → girderToken cookie. An emptyGirder-Token header still takes the header path — the cookie is never consulted as a fallback. So even if a valid HttpOnly cookie is present, a request from this client with this.token === "" will 401.
Fix: in the interceptor, skip adding the header when this.token is falsy, so the request goes out with no Girder-Token and Girder falls through to the cookie.
withCredentials is only attached to the user/authentication call, and is gated on the authenticateWithCredentials option (default false). Every other request goes out without credentials. This means:
Cookies are never sent cross-origin on any non-login request, so a deployment where the front-end origin differs from the Girder origin (dev typically, multi-origin prod sometimes) cannot use cookie-based auth at all.
The Set-Cookie returned by /user/authentication itself is silently dropped by the browser when the login request is cross-origin and authenticateWithCredentials is left at its default false. So the HttpOnly cookie never gets persisted to begin with.
Fix: apply withCredentials: this.authenticateWithCredentials to all requests via the interceptor, not just login. (Or, even better, default authenticateWithCredentials to true — there's no realistic CSRF surface on /user/me etc., and the current default makes cookie auth essentially decorative in cross-origin setups.)
Combined symptom
In a hash-routed SPA on a different origin from Girder, after PR #3484:
User logs in. Set-Cookie: girderToken=...; HttpOnly is returned, but because authenticateWithCredentials defaulted to false and the request was cross-origin, the browser drops it. JS reads the token from the body and stays "logged in" via in-memory state + a JS-set host-only cookie at the front-end origin.
User reloads. New RestClient instance. cookies.get('girderToken') at the front-end origin returns the JS-set cookie if the browser kept it (Chrome's localhost-port handling is inconsistent here; HttpOnly conflict on same name can also block the JS set). If not, falls to setCookieFromHash (issue Use preferred shorthand yarn commands #1) → returns garbage.
@girder/components@4.0.0(and likely earlier v4 releases) has three interacting issues insrc/utils/restClient.jsthat, together, prevent reload-survives-auth when the Girder backend setsgirderTokenasHttpOnly(the default since girder/girder#3484, Dec 2023) and the front-end is served from a different origin than Girder (very common in dev: Vite/webpack on:5173/:8080→ Girder on a different port).1.
setCookieFromHashreturns the entire URL hash as a "token" when no marker is presentrestClient.js:19-29:When
window.location.hashis e.g.#/some/route/123(i.e., any hash-routed SPA), the function returns"/some/route/123"as a "token". That value is then stored inthis.tokenand sent asGirder-Token: /some/route/123on every request → 401.Pre-#3484, this code was dead in practice because
cookies.get('girderToken')always succeeded first. Post-#3484, the JS cookie is gone, so this fallback runs and trips on the SPA's route hash.Fix: only return the parsed token if it matched the marker; otherwise return
null/empty.2. The request interceptor sends
Girder-Tokeneven when empty, suppressing cookie authrestClient.js:51-58:Girder's auth precedence is
?token=→Girder-Tokenheader →girderTokencookie. An emptyGirder-Tokenheader still takes the header path — the cookie is never consulted as a fallback. So even if a valid HttpOnly cookie is present, a request from this client withthis.token === ""will 401.Fix: in the interceptor, skip adding the header when
this.tokenis falsy, so the request goes out with noGirder-Tokenand Girder falls through to the cookie.3.
withCredentialsis only set on the login callrestClient.js:107:withCredentialsis only attached to theuser/authenticationcall, and is gated on theauthenticateWithCredentialsoption (defaultfalse). Every other request goes out without credentials. This means:/user/authenticationitself is silently dropped by the browser when the login request is cross-origin andauthenticateWithCredentialsis left at its defaultfalse. So the HttpOnly cookie never gets persisted to begin with.Fix: apply
withCredentials: this.authenticateWithCredentialsto all requests via the interceptor, not just login. (Or, even better, defaultauthenticateWithCredentialstotrue— there's no realistic CSRF surface on/user/meetc., and the current default makes cookie auth essentially decorative in cross-origin setups.)Combined symptom
In a hash-routed SPA on a different origin from Girder, after PR #3484:
Set-Cookie: girderToken=...; HttpOnlyis returned, but becauseauthenticateWithCredentialsdefaulted tofalseand the request was cross-origin, the browser drops it. JS reads the token from the body and stays "logged in" via in-memory state + a JS-set host-only cookie at the front-end origin.RestClientinstance.cookies.get('girderToken')at the front-end origin returns the JS-set cookie if the browser kept it (Chrome's localhost-port handling is inconsistent here; HttpOnly conflict on same name can also block the JS set). If not, falls tosetCookieFromHash(issue Use preferred shorthand yarn commands #1) → returns garbage.Girder-Token: <garbage>(or withGirder-Token:empty if the consumer sanitized Use preferred shorthand yarn commands #1) → 401. Cookie fallback never engages because of issues REST client utility #2 and CircleCI integration #3.Suggested patch
Reproducible with any Vue 3 + Vue Router (hash mode) + Girder 5.x backend on a separate origin from the front-end.
Versions:
@girder/components: 4.0.0