Skip to content

feat(dashboard): auto-initiate portal SSO redirect on unauthenticated load - #54713

Closed
benbarclay wants to merge 1 commit into
mainfrom
feat/dashboard-sso-autoredirect
Closed

feat(dashboard): auto-initiate portal SSO redirect on unauthenticated load#54713
benbarclay wants to merge 1 commit into
mainfrom
feat/dashboard-sso-autoredirect

Conversation

@benbarclay

Copy link
Copy Markdown
Collaborator

What

When the Hermes Agent dashboard gateway has no local session cookie, it rendered a click-through /login interstitial — even though the Nous portal's /oauth/authorize auto-approves any current member of the dashboard's org (no consent UI) and is a silent 302 when the user already holds a portal session.

So for the common case — clicking a hosted-agent dashboard link while signed in to the portal — that interstitial click is pure friction. This change makes the gate auto-initiate the OAuth redirect on an unauthenticated HTML document load instead.

How

  • In gated_auth_middleware, the no_cookie HTML branch now tries _auto_sso_response() before falling back to the /login interstitial.
  • Auto-SSO fires only when: it's an HTML document load (not an /api/* fetch), exactly one interactive provider is registered, and the one-shot loop-guard marker is absent.
  • Loop-guard: a short-lived hermes_sso_attempt cookie (60s) is set on the bounce. If the user returns from the portal still unauthenticated (no portal session), the marker is present → we fall back to /login exactly once and clear the marker. No infinite ping-pong.
  • The marker is also cleared on a successful /auth/callback.
  • The redirect carries the X-Forwarded-Prefix and the same-origin next= exactly as the old /login redirect did.

Security

This removes a human click, not a security check. The redirect lands on the existing /auth/login route and runs the unchanged PKCE auth-code flow — token verification, aud checks, redirect-URI match, and org-membership checks are all untouched. /api/* fetches still get the 401 JSON envelope (never a 302 a fetch() would follow opaquely into the cross-origin OAuth dance). With two or more providers, the /login chooser still renders (we can't pick for the user).

Tests

  • TestAutoSsoRedirect (new): unauth HTML load auto-redirects + arms the guard; second load with guard present falls back to /login and clears the marker (the loop-safety test); /api/* never auto-redirects; multiple providers render the chooser.
  • Updated the existing HTML-redirect assertions in test_dashboard_auth_401_reauth.py, test_dashboard_auth_middleware.py, and test_dashboard_auth_prefix.py to reflect the new /auth/login target (the prefix-carrying and next= invariants are preserved — only the target path moved).
  • All three touched test files pass together (99/99). Note: this repo has a pre-existing cross-file test-ordering issue in test_dashboard_auth_password_login.py (3 tests fail only in a combined run, pass in isolation) — confirmed identical on clean main with this branch's changes stashed, so it is unrelated to this PR.

Phase 1 of the cloud-auto-discovery work.

… load

When the dashboard gateway has no local session cookie, it rendered a
click-through /login interstitial — even though the Nous portal's
/oauth/authorize auto-approves any current member of the dashboard's org
and is a silent 302 when the user already holds a portal session. For the
common case (clicking a hosted-agent dashboard link while signed in to the
portal) that interstitial click is pure friction.

This makes the gate auto-initiate the OAuth redirect on an unauthenticated
HTML document load instead of rendering the interstitial, when exactly one
interactive provider is registered. A one-shot loop-guard cookie
(hermes_sso_attempt, 60s TTL) ensures that a genuinely absent portal
session (the portal bounces back still-unauthenticated) falls back to the
/login page after exactly one bounce rather than ping-ponging forever. The
marker is cleared on a successful callback and whenever the gate falls back
to /login.

Security: this removes a human CLICK, not a security check. The redirect
lands on the existing /auth/login route and runs the unchanged PKCE
auth-code flow; token verification, audience checks, redirect-URI match,
and org-membership checks are all untouched. /api/* fetches still get the
401 JSON envelope (never a 302 a fetch() would follow opaquely), and with
two or more providers the /login chooser still renders.

Phase 1 of the cloud-auto-discovery work.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: feat/dashboard-sso-autoredirect vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11717 on HEAD, 11717 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 6153 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/dashboard Web dashboard / control panel UI (dashboard/, landing) area/auth Authentication, OAuth, credential pools labels Jun 29, 2026
@benbarclay
benbarclay marked this pull request as ready for review June 29, 2026 08:11

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: LGTM

Auto-SSO redirect for dashboard: when exactly one interactive provider is registered, unauthenticated HTML loads auto-initiate the portal OAuth redirect instead of showing the /login interstitial. A one-shot cookie guard prevents infinite redirect loops when the portal has no session.

Looks Good

  • Loop guard (SSO_ATTEMPT_COOKIE) with 60s TTL prevents ping-pong
  • API paths never auto-redirect (correct — fetch() would follow opaquely)
  • Multiple providers fall back to /login chooser
  • Marker cleared on successful callback and fallback
  • Comprehensive tests: auto-redirect, guard trip, loop safety, API exclusion

Reviewed by Hermes Agent

@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #54846 (commit 61f56d2). Your commit was rebase-merged onto current main with your authorship preserved in git log. One small follow-up on top: dropped the redundant _interactive_providers helper since list_session_providers already filters on supports_session. Thanks Ben!

@teknium1 teknium1 closed this Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants