From bab14d7eb5cf1a169168b65b51fe8f648dee5c2d Mon Sep 17 00:00:00 2001 From: Nico <32375741+nulmete@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:45:51 -0300 Subject: [PATCH] Fix auth token not persisting over HTTP (non-TLS) deployments (#47076) **Related issue:** Resolves #41641 & Resolves #44276 When Fleet is served over plain HTTP from a non-localhost host (e.g. a Docker deployment accessed by IP), login fails with an "Authentication Required" error. To reproduce this, I ran the server as follows: ``` ./build/fleet serve --dev --dev_license --server_tls=false --server_address=0.0.0.0:8080 ``` And then, go to the Fleet UI using my private IP: ``` http://:8080 ``` https://github.com/user-attachments/assets/09543b9b-b9ee-4d1c-b47e-ebd49c20c699 The auth token is stored client-side in a `__Host-token` cookie with the `Secure` attribute, and browsers silently drop `__Host-`/`Secure` cookies on insecure, non-localhost origins. So, the token was never persisted and the follow-up `GET /config` (and every subsequent request) went out without it. This change keeps the `__Host-token` + `Secure` cookie on HTTPS, and falls back to a plain `token` cookie over HTTP so the token persists. TL;DR: this restores the pre [#40504](https://github.com/fleetdm/fleet/pull/40504) behavior for non-TLS deployments and leaves HTTPS behavior unchanged. # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [x] QA'd all new/changed functionality manually https://github.com/user-attachments/assets/e7e838f3-f423-4e28-aebd-5f921af08b00 ## Summary by CodeRabbit * **Bug Fixes** * Fixed an issue where login requests would fail with an "Authentication Required" error when Fleet is served over HTTP. --- changes/41641-token-not-being-passed-onto | 1 + frontend/utilities/auth_token/index.ts | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 changes/41641-token-not-being-passed-onto diff --git a/changes/41641-token-not-being-passed-onto b/changes/41641-token-not-being-passed-onto new file mode 100644 index 0000000000..a4b3b0dfa8 --- /dev/null +++ b/changes/41641-token-not-being-passed-onto @@ -0,0 +1 @@ +- Fixed login failing with an "Authentication Required" error when Fleet is served over HTTP, by storing the auth token in a non-secure cookie outside of HTTPS contexts. diff --git a/frontend/utilities/auth_token/index.ts b/frontend/utilities/auth_token/index.ts index f01848a82a..5749f0488f 100644 --- a/frontend/utilities/auth_token/index.ts +++ b/frontend/utilities/auth_token/index.ts @@ -6,23 +6,36 @@ import Cookie from "js-cookie"; const DEFAULT_EXPIRATION_DAYS = 5; +// The `__Host-` cookie name prefix and the `Secure` attribute both require the +// cookie to be set from a secure (HTTPS) context. When Fleet is served over +// plain HTTP (e.g. a Docker deployment without TLS), the browser silently +// refuses to store such a cookie, leaving the user unable to authenticate +// because the token is never persisted and therefore never attached to +// subsequent requests. Detect the context and fall back to a regular, +// non-secure cookie when not served over HTTPS. +const isSecure = (): boolean => window.location.protocol === "https:"; + +// `__Host-` prefixed names are only valid on secure cookies, so the cookie name +// must match the context it was stored in for get/remove to find it. +const getTokenName = (): string => (isSecure() ? "__Host-token" : "token"); + const save = (token: string, expiresAt?: Date): void => { - Cookie.set("__Host-token", token, { - secure: true, + Cookie.set(getTokenName(), token, { + secure: isSecure(), sameSite: "lax", expires: expiresAt ?? DEFAULT_EXPIRATION_DAYS, }); }; const get = (): string | null => { - return Cookie.get("__Host-token") || null; + return Cookie.get(getTokenName()) || null; }; const remove = (): void => { // NOTE: the secure and sameSite from the cookie must be provided // to correctly remove. That is why we include the options here as well. - Cookie.remove("__Host-token", { - secure: true, + Cookie.remove(getTokenName(), { + secure: isSecure(), sameSite: "lax", }); };