From f9060193fc015c448fe0038868baf8ab1f34c289 Mon Sep 17 00:00:00 2001 From: Nabil Ameena Date: Thu, 24 Sep 2026 00:18:42 +0530 Subject: [PATCH 1/2] fix(ui): store SSO exchange token via storeLoginToken to keep Secure flag exchangeLoginCode wrote the session cookie by hand with path=/ and no Secure flag, replacing the server-set Secure cookie from /v3/login/exchange on https deployments. Use storeLoginToken like loginCall does: UI-path cookie with Secure on https plus sessionStorage fallback, leaving the server cookie untouched. --- .../src/components/networking.test.ts | 34 +++++++++++++++++++ .../src/components/networking.tsx | 3 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/networking.test.ts b/ui/litellm-dashboard/src/components/networking.test.ts index e14f1939ee1..4cb9ea2b2ec 100644 --- a/ui/litellm-dashboard/src/components/networking.test.ts +++ b/ui/litellm-dashboard/src/components/networking.test.ts @@ -118,6 +118,40 @@ describe("loginCall - storeLoginToken integration", () => { }); }); +describe("exchangeLoginCode - storeLoginToken integration", () => { + const originalFetch = global.fetch; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + global.fetch = originalFetch; + }); + + it("calls storeLoginToken when exchange response includes token", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ token: "sso-jwt" }), + }) as unknown as typeof global.fetch; + const { storeLoginToken } = await import("@/utils/cookieUtils"); + const token = await Networking.exchangeLoginCode("some-login-code"); + expect(token).toBe("sso-jwt"); + expect(storeLoginToken).toHaveBeenCalledWith("sso-jwt"); + }); + + it("does not call storeLoginToken when exchange response has no token", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({}), + }) as unknown as typeof global.fetch; + const { storeLoginToken } = await import("@/utils/cookieUtils"); + const token = await Networking.exchangeLoginCode("some-login-code"); + expect(token).toBeUndefined(); + expect(storeLoginToken).not.toHaveBeenCalled(); + }); +}); + describe("modelInfoCall", () => { let currentFetch: typeof global.fetch; diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 3f674ea3328..9c45e12589d 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -7240,7 +7240,8 @@ export const exchangeLoginCode = async (code: string, workerBaseUrl?: string | n const data = await response.json(); if (data.token) { - document.cookie = `token=${data.token}; path=/; SameSite=Lax`; + // UI-path copy only; don't overwrite the server-set cookie at "/" (Secure on https) with a non-Secure one + storeLoginToken(data.token); } return data.token; }; From 4d30621c8c8769aa92df0f231f553afca60a38e2 Mon Sep 17 00:00:00 2001 From: Nabil Ameena Date: Thu, 24 Sep 2026 00:28:56 +0530 Subject: [PATCH 2/2] fix(ui): drop explanatory comment from exchangeLoginCode --- ui/litellm-dashboard/src/components/networking.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 9c45e12589d..95bff0dbf76 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -7240,7 +7240,6 @@ export const exchangeLoginCode = async (code: string, workerBaseUrl?: string | n const data = await response.json(); if (data.token) { - // UI-path copy only; don't overwrite the server-set cookie at "/" (Secure on https) with a non-Secure one storeLoginToken(data.token); } return data.token;