fix: OIDC desktop auth via backend polling instead of localStorage#1
fix: OIDC desktop auth via backend polling instead of localStorage#1
Conversation
Co-authored-by: mechermu <189133602+mechermu@users.noreply.github.com>
|
@copilot peux-tu compiler pour windows que je le teste ? |
Je ne peux pas générer le package Electron Windows depuis cet environnement Linux (la compilation croisée Electron nécessite Wine ou un vrai Windows). Voici deux options pour tester : Option 1 — Build local (si tu as Node.js ≥ 20 et Go ≥ 1.24 installés) : git checkout copilot/fix-oidc-authentication-flow
npm run app:package:win
# L'installeur sera dans app/dist/Option 2 — Workflow GitHub Actions existant ( |
|
@copilot |
Summary
In Electron desktop mode, OIDC auth opens the system browser for the full flow (including MFA). The auth cookie and
localStoragewrite happen in the browser's session, never reaching Electron — so auth never completes. This PR replaces thelocalStoragelistener with a backend polling mechanism that sets the cookie directly in Electron's HTTP session.Related Issue
Changes
Backend (
backend/cmd/headlamp.go)oidcAuthResultstruct +oidcResultMap(mutex-protected) as a temporary in-memory token store (5-min TTL, one-time-use)/oidc-callback: in desktop mode (!UseInCluster), stores token inoidcResultMapand returns an HTML success page instead of redirectingGET /oidc-token-poll?cluster={cluster}endpoint (registered before the static catch-all):202 {"status":"pending"}while waiting200 {"status":"success","cluster":"..."}+Set-Cookieonce token is ready, then deletes entryFrontend (
frontend/src/components/oidcauth/OauthPopup.tsx)window.desktopApi/oidc-token-pollevery 2s withcredentials: 'include'(critical — this is what stores the cookie in Electron's session); callsonCode('success')on result; 5-min timeout; cleans up interval on unmountlocalStoragestorage listener unchangedSteps to Test
Screenshots (if applicable)
Notes for the Reviewer
credentials: 'include'on the poll fetch is load-bearing — without it theSet-Cookieresponse header is ignored by Electron's sessionlocalStoragepath is preserved as-isWarning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
example.com/tmp/go-build3764470664/b1352/serviceproxy.test /tmp/go-build3764470664/b1352/serviceproxy.test -test.testlogfile=/tmp/go-build3764470664/b1352/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true -c=4 -nolocalimports -importcfg /tmp/go-build3764470664/b1305/importcfg -pack /home/REDACTED/go/pkg/mod/k8s.io/client-go@v0.33.3/kubernetes/typed/core/v1/fake/doc.go /home/REDACTED/go/pkg/mod/k8s.io/client-go@v0.33.3/kubernetes/typed/core/v1/fake/fake_componentstatus.go(dns block)go.opentelemetry.io/usr/bin/go go get -v ./... --save-callback-to-file /tmp/callback-3827-x96WxKK555Zz-code-review.json --custom-instructions /tmp/custom-instructions-3827-5w5O5bLDcMjX-.json int.go /structtag -lang=go1.23 ache/go/1.24.13/x64/pkg/tool/linux_amd64/compile slin�� x src/ ../app/electron ../plugins/headlamp-plugin --ignore-pattern ../plugins/headlamp-plugin/te/usr/bin/as -trimpath 1/x64/lib/node_modules/npm/node_modules/@npmcli/run-script/lib/node-gyp-bin/sh adlamp-plugin/te/bin/sh ls/stylecheck/st-c -lang=go1.18 277566/b836/importcfg(dns block)kubernetes-sigs.github.io/tmp/go-build3764470664/b1270/helm.test /tmp/go-build3764470664/b1270/helm.test -test.testlogfile=/tmp/go-build3764470664/b1270/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true om/pmezard/go-di-errorsas .cfg ux_amd64/vet -p theus/client_gol-atomic -lang=go1.22 ux_amd64/vet -p 4470664/b752/_pk-errorsas .cfg ux_amd64/vet -I chr/testify/asse-a -I ux_amd64/vet(dns block)/tmp/go-build3733822276/b1270/helm.test /tmp/go-build3733822276/b1270/helm.test -test.testlogfile=/tmp/go-build3733822276/b1270/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true ake/doc.go ake/fake_node_client.go(dns block)/tmp/go-build965747969/b1270/helm.test /tmp/go-build965747969/b1270/helm.test -test.testlogfile=/tmp/go-build965747969/b1270/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true -unreachable=false /tmp/go-build3764470664/b038/vet.cfg x64/pkg/tool/linux_amd64/vet rce/v1alpha3/docas(dns block)test-cluster.example.com/tmp/go-build3764470664/b001/cmd.test /tmp/go-build3764470664/b001/cmd.test -test.testlogfile=/tmp/go-build3764470664/b001/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true lient-go@v0.33.3-errorsas .cfg ux_amd64/vet -p pkg/postrender -lang=go1.18 ux_amd64/vet 8920�� 8920915/b1213/_p-errorsas .cfg ux_amd64/vet --gdwarf-5 /informers/resou-atomic -o ux_amd64/vet(dns block)/tmp/go-build3733822276/b001/cmd.test /tmp/go-build3733822276/b001/cmd.test -test.testlogfile=/tmp/go-build3733822276/b001/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true se 4470664/b135/vetgithub.com/kubernetes-sigs/headlamp/backend/pkg/helm_test ux_amd64/vet -p k8s.io/client-go-o -lang=go1.24 ux_amd64/vet --64�� -o /tmp/ccjDpZ8v.o ux_amd64/vet -c=4 -nolocalimports -importcfg ux_amd64/vet(dns block)/tmp/go-build965747969/b001/cmd.test /tmp/go-build965747969/b001/cmd.test -test.testlogfile=/tmp/go-build965747969/b001/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true -o /tmp/ccoytRMt.o ux_amd64/vet go ency.go(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
Problem
When using OIDC authentication (e.g., Entra ID/Keycloak) in Headlamp Desktop (Electron) on Windows, the authentication flow fails because:
The
OauthPopupcomponent callswindow.open()which Electron intercepts and opens in the system browser (viashell.openExternal) because the URL (http://localhost:{port}/oidc?...) doesn't match thestartUrl(file://...).After the user authenticates (including MFA), the OIDC callback (
/oidc-callback) lands in the system browser. The backend sets the auth cookie (Set-Cookie) in the browser's HTTP response, but this cookie belongs to the browser's session, not Electron's session.The
OIDCAuthReact component writeslocalStorage.setItem('auth_status', 'success')in the browser's localStorage, not Electron's. ThestorageListenerinOauthPopup(running in Electron) never sees the change.Result: authentication never completes in the Electron app, and even if it did, the token cookie would not be available for subsequent API calls from Electron.
Related issue: kubernetes-sigs#2398
Solution
Implement a polling-based token transfer mechanism for Desktop mode that:
1. Backend changes (
backend/cmd/headlamp.go)In the
/oidc-callbackhandler, when running in desktop mode (!config.UseInCluster):oidcResultMap) keyed by cluster name, with a one-time-use semantic and expiration (5 minutes)./auth?cluster=....Add a new endpoint
GET /oidc-token-poll?cluster={cluster}:oidcResultMap:auth.SetTokenCookie()on the response (this sets the cookie in the Electron session since the request comes from Electron'sfetch()).{"status": "success", "cluster": "..."}with HTTP 200.{"status": "pending"}with HTTP 202.The
oidcResultMapshould be protected by a mutex (sync.Mutex), similar to howoauthRequestMapis already implemented.The
oidcAuthResultstruct should contain:2. Frontend changes (
frontend/src/components/oidcauth/OauthPopup.tsx)In the
createPopupfunction, detect desktop mode viawindow.desktopApi:window.open(url)(which Electron will open in system browser — existing behavior).GET /oidc-token-poll?cluster={cluster}every 2 seconds withcredentials: 'include'(so theSet-Cookiefrom the response is stored in Electron's session).{"status": "success"}, stop polling and callonCode('success').window.open()+localStoragelistener behavior unchanged.Clean up the poll interval in the
useEffectcleanup function.Why this works
fetch()call from Electron to/oidc-token-pollreceives theSet-Cookieheader, which stores the auth cookie in Electron's HTTP session (not the browser's).fetch('/clusters/k8s/api/v1/pods', {credentials: 'include'})) will include this cookie.setTokenFromCookie) reads the cookie from the request and sets theAuthorization: Bearer {token}header before proxying to the kube-apiserver.localStorage+ popup flow continues to work.Key files to modify
backend/cmd/headlamp.go— AddoidcResultMap, modify/oidc-callbackhandler for desktop mode, add/oidc-token-pollendpointfrontend/src/components/oidcauth/OauthPopup.tsx— Add desktop mode detection and polling logicImportant considerations
/oidc-token-pollendpoint must be registered on the router BEFORE the static file catch-allr.PathPrefix("/")handler.credentials: 'include'option on the fetch is CRITICAL — without it, the browser won't store the cookie from the response.localStoragelisten...This pull request was created from Copilot chat.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.