Skip to content

Fleet Desktop SSO: My device page SSO flow #51523

Description

@juan-fdz-hawa

Related user story

#47116

Task

Teaches the My device page (DeviceUserPage) the SSO dance: when a device API call returns the SSO-required 401, initiate device SSO and navigate to the IdP; after the callback redirects back with the session cookie, the page loads normally. Handles expired sessions (mid-visit 401 → re-initiate) and SSO failure states. No Figma exists for this surface (spec open question 5) — states below are the proposed minimum; confirm with product before polishing.

Context for a fresh session: read tmp/47116/spec.md (spec artifacts in the spec lead's local checkout; gitignored) ("Device SSO session and cookies" narrative). Contracts from sub-issues 2/3:

  • POST /api/_version_/fleet/device/{token}/sso{"url": "<IdP URL>"} + handshake cookie; SSO-exempt, device-token-authenticated.
  • Gated endpoints return HTTP 401 with "sso_required": true in the JSON body when the setting is on and no valid session cookie accompanies the token. A plain 401 (no marker) still means invalid/expired token → existing error page.
  • After IdP auth, the server 303s the browser back to /device/{token}; the session cookie is set server-side — the SPA never touches it.

Detect SSO-required

frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx — the page already special-cases auth failures: isAuthenticationError derived from the device-details query error at :360-361, rendered via DeviceUserError (frontend/components/DeviceUserError/DeviceUserError.tsx:48-64, "This URL is invalid or expired."). Bonus signal: during Setup Experience, orbit opens the page with ?setup_only=1 (orbit/pkg/setup_experience/setup_experience.go:187) — the server-side carve-out (sub-issue 3) makes SSO a non-issue there, but the SPA can use the param as defense in depth to avoid ever auto-initiating in that mode. Extend the error classification: parse the API error body for sso_required: true (check how services/entities/device_user.ts surfaces error bodies — follow the existing isDupDetailsError handling) and branch:

  • sso_required → start the SSO flow (below) instead of rendering the invalid-URL error.
  • 401 without the marker → existing invalid-URL error, unchanged.

Apply the same classification to the other device queries the page issues (policies, software, certificates) so a mid-visit session expiry re-initiates rather than erroring — centralize in one helper rather than per-query copies.

Initiate and redirect

Add the route to frontend/utilities/endpoints.ts (it is not there yet), then the service method in frontend/services/entities/device_user.ts. Note the existing methods there build URLs from DEVICE_USER_DETAILS as a string prefix (device_user.ts:71-89, e.g. `${DEVICE_USER_DETAILS}/${token}/refetch`) — follow that pattern:

initiateDeviceSSO: (deviceAuthToken: string) => {
  const { DEVICE_USER_DETAILS } = endpoints;
  return sendRequest("POST", `${DEVICE_USER_DETAILS}/${deviceAuthToken}/sso`);
},

Flow component/state on DeviceUserPage:

  1. On sso_required: call initiateDeviceSSO(deviceAuthToken), then window.location.href = response.url — same pattern as frontend/pages/MDMAppleSSOPage/MDMAppleSSOPage.tsx (calls initiate, navigates to the returned IdP URL).
  2. While initiating/navigating: render a lightweight "Redirecting to your organization's sign-in page…" state (Spinner + text; reuse the page's existing loading treatment). Avoid flashing the full page skeleton first — classify before first render where practical.
  3. Loop guard: if the page loads, initiates, comes back, and still gets sso_required (cookie blocked, third-party-cookie policies, clock skew), do not re-initiate infinitely. Track one automatic attempt (e.g. sessionStorage flag keyed by token) and on the second consecutive failure render a terminal error state with a "Sign in again" button for manual retry.
  4. Initiate-call failures (network, 422 feature-off race, free tier) → terminal error state with retry; copy TBD with product (open question 5), placeholder: "Your organization requires single sign-on to view this page. We couldn't start sign-in — try again or contact your IT admin."

Return leg

Nothing to build: the server 303s back to /device/{token} and the queries just succeed. Verify the router treats the return URL identically (device routes at frontend/router/index.tsx:457-466, outside the authenticated/unauthenticated wrappers) and that any sub-route the user started on (/device/{token}/self-service, /software, /policiesfrontend/router/paths.ts:187-199) survives the round-trip: the initiate endpoint's originalURL comes from the server; pass the current full path if sub-issue 2 supports it, else landing on the root device page is acceptable v1.

States summary (proposed minimum — no Figma)

State Treatment
SSO required, first load Auto-initiate + "Redirecting to your organization's sign-in page…"
Session expired mid-visit Same auto-initiate on the first sso_required response
Second consecutive sso_required Terminal error card + "Sign in again" button
Initiate call fails Terminal error card + retry
Plain 401 (bad token) Existing "This URL is invalid or expired." — unchanged

Condition of satisfaction

Flow:

  • With the gate on and no session: loading /device/{token} initiates SSO and navigates to the IdP URL; on return the page renders fully with no extra prompt.
  • Deep link (/device/{token}/self-service) returns the user to a working page after SSO (root landing acceptable if originalURL doesn't carry sub-routes).
  • Mid-visit expiry: a gated query returning sso_required re-initiates once automatically.
  • Loop guard: consecutive sso_required after a round-trip renders the terminal state with manual retry — no redirect loop (test by blocking cookies).
  • Plain invalid-token 401 renders the existing DeviceUserError, pixel-identical to today.
  • Setting off: zero behavior change on the page (no new requests, no flashes).

Tests/commands:

  • yarn test — DeviceUserPage tests cover: sso_required classification vs plain 401, auto-initiate call + redirect, loop guard, terminal states.
  • make lint-js clean.
  • Manual: full loop against a dev server with sub-issues 1–3 merged and a test IdP (see tmp/47116/spec.md Testing tips, steps 4–7).

Metadata

Metadata

Assignees

Labels

#g-supply-chainSupply Chain product group~frontendFrontend-related issue.~sub-taskA technical sub-task that is part of a story. (Not QA'd. Not estimated.)

Type

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions