Try SSO silently on an anonymous visit when FLEET_OIDC_AUTO_START is set - #1514
Merged
Merged
Conversation
TLDR With FLEET_OIDC_AUTO_START=1, a browser that opens Fleet without a session is sent to the identity provider first with a silent check (OIDC prompt=none). If it already has a central session it lands in Fleet without clicking anything; if not, it comes back to Fleet's normal login card with the SSO button and the password form, no error shown. The card, and with it the local admin password, stays one URL away, so nothing about recovery when Auth is down changes. What changed, and why Elcano's central Auth signs a user in once for every application; Explorer already starts SSO on any anonymous visit. Fleet could not copy that because it keeps its own email/password login as the break-glass path, and an unconditional redirect would strand a user without a central session on the IdP's password page instead of Fleet's page with both options. prompt=none is the protocol's answer: the IdP either returns a code or error=login_required, and Fleet decides what to render. - getOidcConfig gains autoStart (FLEET_OIDC_AUTO_START, off by default); shouldAutoStartLogin(params, config) is the one decision point. - /api/auth/oidc/start?silent=1 adds prompt=none to the authorize request; everything else about the request is unchanged. - The callback maps error=login_required and error=interaction_required to a 303 to /login?sso=none with the temp cookies cleared: no banner, and the sso=none marker stops the page from auto-starting again. Every other IdP error keeps the existing oidc_denied handling. - /login (server component) reads its search params and, when auto-start is on and the visit carries no ?e= (a login error), no ?sso=none and no ?manual=1, redirects to the silent start. The prop is optional so callers that render the page without params (tests) are unchanged. - Docs: DEPLOYMENT.md lists the flag; central-auth-integration.md describes the flow and notes that Auth supports prompt=none from 1521760+ (an older Auth ignores the parameter and shows its own login form, a degradation, not a failure). How you verified it - Vitest: 61 tests across the oidc lib, start and callback routes and the login page pass, including new ones: flag parsing and shouldAutoStartLogin cases; silent start adds prompt=none and a plain start does not; login_required / interaction_required → /login?sso=none with cookies cleared and no session minted; the page redirects to /api/auth/oidc/start?silent=1 on a plain visit with the flag set, renders the card with both options for ?sso=none, ?manual=1 and ?e=, and never auto-starts with the flag off. - npm run lint (oxlint) and npm run typecheck (tsc) clean. npm run build and the Playwright suites were left to CI. Scope and deviations Opt-in flag only; default behaviour is unchanged. No ADR: no invariant is touched (login still goes through the same routes, cookies and membership check). The user guides do not describe the login card, so none needed updating. Deferred: replacing AUTH_SIGNING_PUBKEY as the legacy magic-link toggle with an explicit flag (separate change).
4 tasks
jzhao234
added a commit
that referenced
this pull request
Sep 16, 2026
) ## What changed, and why Signing out of Fleet used to clear only Fleet's cookies. A user signed in through central Auth still had a live Auth session, so with silent SSO auto-start (#1514) the next page load signed them straight back in and "log out" looked broken. `POST /api/auth/logout` still clears `elcano_session` and both shapes of the legacy `elcano_auth` cookie. It now also verifies the presented session cookie: when its source is `oidc` and `FLEET_OIDC_*` is configured, the 303 targets `<issuer>/logout?client_id=<client id>` (OpenID Connect RP-Initiated Logout, implemented by Elcano Auth in ElcanoTek/auth#36) instead of `/login`. On Elcano Auth that ends the central session, fans a back-channel logout out to every registered application, and lands on Auth's login page. Password sessions, and any request without a verifiable cookie, land on `/login?manual=1`: the card with both options and no silent SSO attempt, so a logout cannot be undone by auto-start while an Auth cookie happens to exist. The three OIDC transaction cookies are cleared too, and a malformed issuer falls back to the same local landing instead of failing. Auth's back-channel fan-out then rotates Fleet's external epoch; the cookie is already gone, so that is belt and braces. ## How you verified it - Vitest (logout route, 7 tests): an OIDC session token → 303 to `https://auth.example.com/logout?client_id=fleet` with `elcano_session` cleared; a password session token with OIDC configured → `/login`; the existing cookie-shape tests unchanged. - `npm run lint` and `npm run typecheck` clean; build and Playwright left to CI. ## Scope and deviations No change to the legacy `elcano_auth` handling or to password logout. **Requires Auth with RP-initiated logout deployed first**; an older Auth answers 405 to the GET. No ADR: no invariant touched. Docs: `central-auth-integration.md` describes the flow. --- - [x] The title and "What changed, and why" are written for the release notes they become - [x] A design note added, if this ships a feature (`docs/features/central-auth-integration.md` updated) - [x] An ADR added or superseded, if this adds, weakens or reverses an invariant (none touched) - [x] The diff is scoped to one change
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed, and why
With
FLEET_OIDC_AUTO_START=1, a browser that opens Fleet without a session is sent to the identity provider first with a silent check (OIDCprompt=none). If it already has a central session it lands in Fleet without clicking anything; if not, it comes back to Fleet's normal login card with the SSO button and the password form, no error shown. The card, and with it the local admin password, stays one URL away (/login?manual=1), so nothing about recovery when Auth is down changes.Elcano's central Auth signs a user in once for every application; Explorer already starts SSO on any anonymous visit. Fleet could not copy that because it keeps its own email/password login as the break-glass path, and an unconditional redirect would strand a user without a central session on the IdP's password page instead of Fleet's page with both options.
prompt=noneis the protocol's answer: the IdP either returns a code orerror=login_required, and Fleet decides what to render.getOidcConfiggainsautoStart(FLEET_OIDC_AUTO_START, off by default);shouldAutoStartLogin(params, config)is the one decision point./api/auth/oidc/start?silent=1addsprompt=noneto the authorize request; everything else about the request is unchanged.error=login_requiredanderror=interaction_requiredto a 303 to/login?sso=nonewith the temp cookies cleared: no banner, and thesso=nonemarker stops the page from auto-starting again. Every other IdP error keeps the existingoidc_deniedhandling./login(server component) reads its search params and, when auto-start is on and the visit carries no?e=, no?sso=noneand no?manual=1, redirects to the silent start. The prop is optional so callers that render the page without params are unchanged.central-auth-integration.mddescribes the flow and notes that Auth supportsprompt=nonefrom1521760+ (ElcanoTek/auth#35); an older Auth ignores the parameter and shows its own login form, a degradation, not a failure.How you verified it
shouldAutoStartLogincases; silent start addsprompt=noneand a plain start does not;login_required/interaction_required→/login?sso=nonewith cookies cleared and no session minted; the page redirects to/api/auth/oidc/start?silent=1on a plain visit with the flag set, renders the card with both options for?sso=none,?manual=1and?e=, and never auto-starts with the flag off.npm run lint(oxlint) andnpm run typecheck(tsc) clean.npm run buildand the Playwright suites left to CI.Scope and deviations
Opt-in flag only; default behaviour is unchanged. No ADR: no invariant is touched (login still goes through the same routes, cookies and membership check). The user guides do not describe the login card, so none needed updating. Deferred: replacing
AUTH_SIGNING_PUBKEYas the legacy magic-link toggle with an explicit flag (separate change).docs/features/central-auth-integration.mdupdated)