Repository navigation
[Feature]: persistent WebUI auth on remote binds — configurable session lifetime or non-token login #4055
Description
Area
Dashboard
What are you trying to accomplish?
I operate the dashboard on a remote bind (non-loopback, behind a reverse proxy on a k3s/homelab hub). I open the dashboard from desktop and mobile browsers and need the session to persist across page reloads and browser restarts without re-entering the raw admin token.
What prevents this today?
Per the management-auth docs, session issuance is disabled whenever data-plane authentication is required (remote binds):
Session issuance is disabled whenever data-plane authentication is required, which includes remote binds. A remote operator must authenticate with the raw admin token; no loopback-style GUI session is minted.
Loopback binds get short-lived ocx_session_* credentials (5 minutes), but remote operators are left with the raw admin token prompt on every fresh page load / browser session. There is no configurable session lifetime and no alternative auth path for the remote-bind dashboard.
What should OpenCodex do?
When a remote operator enters the admin token once, the dashboard should issue a persistent GUI session (cookie with SameSite=Lax + HttpOnly) whose lifetime the operator controls, so that reloading the page or reopening the browser does not prompt for the token again until the configured TTL expires.
Concretely, at least one of:
dashboard.sessionTtlMinutesconfig key (default5for loopback parity; remote operators can raise to hours/days at their own risk).- Alternative non-token auth: reverse-proxy Basic auth pass-through, OIDC/SSO callback, or a "remember this device" scoped credential.
Example usage or interface
In ~/.opencodex/config.json:
{
"dashboard": {
"sessionTtlMinutes": 1440
}
}Or via CLI:
ocx config set dashboard.sessionTtlMinutes 1440Before: reload dashboard → window.prompt("OpenCodex API token") on every page load.
After: reload dashboard → session restored from cookie until TTL expires; prompt only appears again after expiry.
Alternatives or workarounds
- Storing the raw admin token in browser localStorage / password-manager autofill — defeats the masking posture the dashboard otherwise maintains (credential redaction in management responses).
- Tunneling the dashboard through an SSH/loopback proxy so the loopback session path applies — awkward on mobile.
Additional context
- Dashboard prompts for the API token once per in-flight /api request on non-loopback binds #647 (closed) fixed the token-prompt-per-request burst; this is the next step — the prompt should not be needed per page load at all.
- [Feature]: Option to toggle or disable email masking for stored accounts in Dashboard and CLI #3859 (open) — email masking UX; same remote-operator trust model.
- Headless hub: WebUI cannot reauth native main (deviceauth is pool-only) #3898 (open) — headless hub reauth gaps; same deployment shape.
Checks
- I searched existing issues and documentation.
- This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
- I removed secrets and personal data.
Activity
github-actions commentedon Sep 8, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
lidge-jun commentedon Sep 8, 2026
리뷰 · 우선순위 46 / 80
이 이슈는 non-loopback(원격 바인드) 대시보드를 k3s/홈랩 리버스 프록시 뒤에서 쓸 때, 페이지를 다시 열거나 브라우저를 재시작할 때마다 raw admin token을 다시 치고 싶지 않다는 요청입니다. 작성자가 인용한 management-api 문구(“data-plane auth가 필요하면 session issuance 비활성 · 원격 운영자는 raw admin token만”)는 문서에 아직 그대로 있고, 체감도 “매 로드마다 토큰 폼”에 가깝습니다. 제안은
dashboard.sessionTtlMinutes로 TTL을 늘리거나, 쿠키(SameSite=Lax+HttpOnly)로 GUI 세션을 유지하거나, Basic/OIDC/“이 기기 기억” 같은 non-token 로그인입니다.지금
devHEAD(164bd2b36, package 2.49.0)의 실제 코드는 문서 한 줄보다 앞서 있습니다. loopback은 API auth가 없을 때만issueGuiSession이ocx_session_*를 만들고 TTL은LOOPBACK_GUI_SESSION_TTL_MS = 5분입니다(src/server/gui-session.ts). 원격에서는 이미 두 갈래가 있습니다. (1)runtimeRole === "hub"+ trusted Tailscale ingress +remoteGui.allowedTailscaleUsers일치 + HTTPS origin 허용이면tailscale-identity세션을 발급하고 TTL은REMOTE_GUI_SESSION_TTL_MS = 12시간. (2) hub pairing grant(ocx_pair_*)를 한 번 쓰고 세션을 받는 경로. remote-hub 가이드도 “admin token은 일반 관리는 되지만 브라우저 session을 mint하거나 consent 행위(별 찍기 등)를 승인하지 못한다”고 못 박습니다. management-api 본문에도 “An admin token … can never mint or exchange into a consent session”이 있습니다.그래서 이슈가 원하는 “admin token 한 번 입력 → 서버가 긴 TTL 쿠키 세션 발급” 은, 지금 제품의 의도적 보안 경계와 정면으로 부딪칩니다. GUI는
gui/src/api.ts에서 세션을 메모리만 유지하고, remote에서 세션을 못 받으면promptForAdminToken(gui/src/admin-token-dialog.ts) 비밀번호 폼을 띄웁니다. 토큰은localStorage/sessionStorage에 안 씁니다(웹 대시보드 문서·#647 이후 자세). 브라우저 비밀번호 관리자 autofill은 이미 허용된 완화책입니다. 쿠키 HttpOnly 세션으로 바꾸려면 bootstrap meta 주입·CSRF·origin 바인딩·logout 계약을 통째로 다시 짜야 합니다.설정 키
dashboard.sessionTtlMinutes는 HEADsrc/config.ts/ types에 없습니다. 넣으면 types/config 분할 캠페인과 겹칠 수 있어, naive config PR은 close-don't-rebase 후보입니다. 이미 있는 원격 TTL(12h)을 이미 발급된 remote/pairing/Tailscale 세션에만 노출·조정하는 얇은 설계는 가능하지만, “admin → session 교환”과는 별개입니다. 관련 #3898(헤드리스 hub에서 native main reauth 구멍), #3859(이메일 마스킹·원격 신뢰 모델)는 같은 배포 모양의 이웃이지 중복이 아닙니다. #647(닫힘)은 요청마다 프롬프트 폭주를 고친 것이고, 이 이슈는 그 다음 단계(로드/재시작 간 지속)를 말합니다.경로
docs-site/.../management-api.md“Session issuance is disabled…” - 문서가 loopback-only 서술에 머물러 있음. Tailscale/pairing 경로와 어긋남경로
src/server/gui-session.tsLOOPBACK_GUI_SESSION_TTL_MS / REMOTE_GUI_SESSION_TTL_MS / issueGuiSession - 원격 세션은 hub+Tailscale(또는 pairing)만. admin token 교환 없음. 원격 TTL 이미 12h경로
src/server/management-auth.tsresolveManagementAdmission -admin-token과gui-sessionprincipal 분리. consent는 세션만경로
docs-site/.../guides/remote-hub.mdTrust boundaries - admin token으로 session mint 금지가 명시 설계경로
gui/src/api.tsstoreSession / 메모리 only - 쿠키·persistent storage 없음. reload 시 bootstrap/rebootstrap 또는 admin 폼경로
gui/src/admin-token-dialog.ts- 비밀번호 폼 + 브라우저 autofill 유도. OCX 자체는 토큰 비지속설정
dashboard.sessionTtlMinutes- 스키마 없음. 추가 시 config/types 분할과 충돌 위험메인테이너의 판단이 필요한 지점
너의 추천
enhancement로 유지하되 design-first. admin-token→session mint PR은 거절. 먼저 management-api/web-dashboard 문장을 HEAD 코드에 맞게 고치세요(원격은 Tailscale allowlist 또는
ocx connectpairing으로 12hgui-session, admin token은 관리 API용·consent 아님). k3s 사용자는 hub management ingress + pairing/Tailscale을 정식 경로로 안내하고, SSH/loopback 터널은 문서화된 워크어라운드로 남기세요. TTL만 늘리고 싶다면 이미 발급된 remote 세션의REMOTE_GUI_SESSION_TTL_MS를 좁은 config로 노출하는 후속만 검토하세요.dashboard.sessionTtlMinutes큰 스키마·쿠키 영속·OIDC는 2.49.x 마감 밖·types/config 분할과 겹치면 닫고 다시. #3898/#3859와 중복 close는 하지 마세요.이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Sep 10, 2026
Additional deployment evidence for the repeated admin-token prompt on refresh:
devat6d3ad12e3(2.51.0).gui/src/api.tskeeps the accepted credential in in-memory runtime session state and explicitly removes the legacy sessionStorage token. Thus reload persistence should not be represented as an existing token-save option.Supported workaround verified without a source patch:
{ "remoteGui": { "allowedTailscaleUsers": ["operator@example.com"] } }After configuring the operator's exact Tailscale login, restarting the hub, and accessing the trusted Tailscale management ingress, the dashboard opened without entering an admin token. Two successive full-page reloads retained working dashboard access by issuing fresh Tailscale-identity sessions. No admin token was persisted in browser storage.
UX suggestion: when the remote admin-token dialog is shown, explain whether the entered credential lasts only until reload and surface the supported Tailscale identity setup (or a bounded persistent-session option if implemented). The current help text explains the token's file location and environment override, but does not help the operator understand why they must paste it repeatedly.
This comment intentionally omits real account names, tailnet addresses, tokens, and session metadata.
lidge-jun commentedon Sep 27, 2026
Closing, because the case reported on 2.51.0 (Tailscale Serve) is covered on
dev. WithremoteGui.allowedTailscaleUserson the Tailscale management ingress, the dashboard gets a 12-hour identity session that each authorized request extends (#2776). A reload bootstraps a fresh session without the token prompt. The management API reference said remote binds never get a session, which was wrong; #6066 (merge7d8459388c) corrects it. A generic reverse proxy without a Tailscale identity still uses the admin token or a pairing grant. That is the intended boundary, since an admin token does not mint browser sessions.docs(management-api): describe remote dashboard sessions as they ship