Skip to content

feat(agentex-ui): OIDC login with server-side access token#351

Open
erichwoo-scale wants to merge 4 commits into
feat/agentex-ui-account-pickerfrom
feat/agentex-ui-oidc-auth
Open

feat(agentex-ui): OIDC login with server-side access token#351
erichwoo-scale wants to merge 4 commits into
feat/agentex-ui-account-pickerfrom
feat/agentex-ui-oidc-auth

Conversation

@erichwoo-scale

@erichwoo-scale erichwoo-scale commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Second of a 2-PR stack. Stacked on #350base is feat/agentex-ui-account-picker, so this diff is auth-only (GitHub auto-retargets to main once #350 merges). The chart + deploy wiring lives in scaleapi/sgp#3961.

Adds opt-in OIDC login on top of the BFF, keeping the access token out of the browser entirely.

  • NextAuth (generic OIDC provider) selected and enabled by a single env var, AGENTEX_UI_AUTH_PROVIDER_ID; disabled by default so non-auth deployments are unaffected (no SessionProvider, no /api/auth/session calls).
  • The BFF attaches the access token as a Bearer server-side via getToken and drops the UI session cookie — the token never reaches client JS or the NextAuth session (which exposes only error).
  • Token/end-session endpoints are resolved from the issuer's OIDC discovery document (cached; failures retried), so refresh and logout work for any compliant IdP — no hardcoded provider paths.
  • Middleware auto-redirects unauthenticated requests to sign-in; a client guard re-auths on refresh failure; POST-only RP-initiated logout ends the IdP session (a GET would be cross-site-triggerable — logout CSRF).
  • Supports client_secret_post (dev) and private_key_jwt (prod).

Why token-hidden (BFF) over exposing it on the session

Exposing the access token to client JS is contrary to the OAuth 2.0 for Browser-Based Apps BCP and would flag in security review. Keeping it server-side (the BFF attaches the Bearer) is the recommended pattern; the client only ever sees the same-origin proxy.

Demo

agentex-ui-oidc-auth.mov

Stack

  1. feat(agentex-ui): account picker via same-origin BFF proxy #350 — account picker + BFF proxy
  2. This PR — OIDC login → stacked on feat(agentex-ui): account picker via same-origin BFF proxy #350
  3. scaleapi/sgp#3961 — Helm chart wires the OneAuth OIDC client + env (AGENTEX_UI_AUTH_PROVIDER_ID, OIDC_ISSUER_URL, the client Secret via envFrom)

Test plan

  • npm run typecheck / npm run lint — clean
  • Runtime login smoke test (sign-in → agents load via Bearer → logout ends the IdP session)
  • Chart wiring — done in scaleapi/sgp#3961

🤖 Generated with Claude Code

@erichwoo-scale erichwoo-scale requested a review from a team as a code owner July 8, 2026 18:36
@socket-security

socket-security Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​next-auth@​5.0.0-beta.31991007886100

View full report

Comment thread agentex-ui/auth.ts Outdated
Comment thread agentex-ui/app/api/auth/logout/route.ts Outdated
@erichwoo-scale erichwoo-scale force-pushed the feat/agentex-ui-oidc-auth branch from bb896c7 to c53787d Compare July 8, 2026 19:21
@erichwoo-scale

Copy link
Copy Markdown
Contributor Author

Addressed the Greptile P1s in c53787d:

  • Hardcoded token endpoint (auth.ts) and hardcoded end_session_endpoint (logout/route.ts): both are now resolved from the issuer's .well-known/openid-configuration (cached per-process), with the Ory paths kept only as a last-resort fallback. This fixes the silent refresh→re-auth loop and the transparent re-login on non-Ory IdPs, and makes the "generic OIDC" claim actually hold.

Rebased onto the updated base branch so the stack stays clean; the footer now renders Give Feedback → Account Picker → Log out (account picker placement lands in #350).

@erichwoo-scale erichwoo-scale force-pushed the feat/agentex-ui-oidc-auth branch 2 times, most recently from 6e749ef to db8bec0 Compare July 8, 2026 19:31
Comment thread agentex-ui/app/api/auth/logout/route.ts Outdated
@erichwoo-scale erichwoo-scale force-pushed the feat/agentex-ui-oidc-auth branch from db8bec0 to 00d2195 Compare July 8, 2026 19:40
@erichwoo-scale erichwoo-scale force-pushed the feat/agentex-ui-account-picker branch from 8eb5b47 to 5f243a0 Compare July 8, 2026 20:00
@erichwoo-scale erichwoo-scale force-pushed the feat/agentex-ui-oidc-auth branch 4 times, most recently from 5d58576 to 054fad9 Compare July 8, 2026 21:31
erichwoo-scale and others added 4 commits July 8, 2026 17:41
Add opt-in OIDC login on top of the BFF, keeping the access token out of the
browser entirely.

- NextAuth (generic OIDC provider) selected + enabled by a single env var,
  `AGENTEX_UI_AUTH_PROVIDER_ID`; disabled by default so non-auth deployments are
  unaffected (no SessionProvider mounts, no /api/auth/session calls).
- The BFF attaches the access token as a Bearer server-side via `getToken` and
  drops the UI session cookie — the token never reaches client JS or the NextAuth
  session (which exposes only `error`).
- Middleware auto-redirects unauthenticated requests to sign-in; a client guard
  re-auths on refresh failure; RP-initiated logout clears the IdP session.
- Supports `client_secret_post` (dev) and `private_key_jwt` (prod).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address Greptile P1s. The refresh path and RP-initiated logout hardcoded Ory's
`/oauth2/token` and `/oauth2/sessions/logout` paths, which 404 on non-Ory IdPs —
silently forcing a re-auth loop (refresh) or a transparent re-login (logout).

Resolve `token_endpoint` and `end_session_endpoint` from the issuer's
`.well-known/openid-configuration` (cached per-process; failures not cached). No
Ory-specific fallback: a missing token_endpoint fails the refresh cleanly (→ re-auth),
and a missing end_session_endpoint means local-only logout rather than a guessed path.
OneAuth (Ory Hydra) advertises both in discovery, and NextAuth already relies on the
same document for sign-in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A GET /api/auth/logout is reachable by a crafted cross-site navigation (SameSite=Lax
sends the session cookie on top-level GET), letting an attacker end the user's IdP SSO
session. Switch to POST (Lax cookies aren't sent on cross-site POST); the client POSTs
and follows the returned RP-initiated logout URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t mode

Trim verbose/redundant comments across auth.ts, middleware, session-guard, layout and the
auth routes; drop provider-specific ("Ory") wording (endpoints come from discovery); rename
the non-auth "legacy mode" to "default mode". No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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