Skip to content

Try SSO silently on an anonymous visit when FLEET_OIDC_AUTO_START is set - #1514

Merged
jzhao234 merged 1 commit into
mainfrom
feat/oidc-auto-start
Sep 15, 2026
Merged

jzhao234 merged 1 commit into
mainfrom
feat/oidc-auto-start

Conversation

@jzhao234

Copy link
Copy Markdown
Contributor

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 (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 (/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=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=, no ?sso=none and no ?manual=1, redirects to the silent start. The prop is optional so callers that render the page without params are unchanged.
  • Docs: DEPLOYMENT.md lists the flag; central-auth-integration.md describes the flow and notes that Auth supports prompt=none from 1521760+ (ElcanoTek/auth#35); 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 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).


  • The title and "What changed, and why" are written for the release notes they become
  • A design note added, if this ships a feature (docs/features/central-auth-integration.md updated)
  • An ADR added or superseded, if this adds, weakens or reverses an invariant (none touched)
  • The diff is scoped to one change

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).
@jzhao234
jzhao234 merged commit b46f7f8 into main Sep 15, 2026
21 checks passed
@jzhao234
jzhao234 deleted the feat/oidc-auto-start branch September 15, 2026 20:17
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant