Skip to content

feat(sso) expired token session redirect#2051

Open
dimetron wants to merge 1 commit into
kagent-dev:mainfrom
dimetron:feature/sso-session-redirect
Open

feat(sso) expired token session redirect#2051
dimetron wants to merge 1 commit into
kagent-dev:mainfrom
dimetron:feature/sso-session-redirect

Conversation

@dimetron

@dimetron dimetron commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Improve SSO expired token redirect (#2045)

This pull request implements robust handling of SSO session expiry in the UI when deployed behind an OIDC proxy (such as oauth2-proxy). The main improvements are the introduction of a clear authentication status model, transparent re-authentication when the session expires, and safeguards against redirect loops. The changes ensure users are not unexpectedly logged out when their forwarded token expires, while avoiding infinite redirects in unsecured (no proxy) environments. Comprehensive unit tests and documentation updates are included.

Authentication state handling and re-authentication flow:

  • Introduced an explicit AuthStatus model ("authenticated", "expired", "unsecured") and updated getCurrentUser to return a structured AuthResult reflecting these states. This allows the UI to distinguish between a missing proxy, an expired token, and a valid session. [1] [2]
  • Updated AuthProvider in AuthContext.tsx to trigger a redirect to the OIDC re-auth endpoint (/oauth2/start) only when the session is "expired", never in "unsecured" mode. Added a sessionStorage-based guard to prevent redirect loops if re-authentication fails to refresh the token. The guard is cleared on successful authentication. [1] [2] [3]

Testing and reliability:

  • Added comprehensive unit tests for getCurrentUser to cover all authentication states, ensuring correct status assignment and token handling.
  • Added tests for AuthProvider to verify redirect and guard logic, including prevention of redirect loops and correct guard clearing on authentication.

Documentation and UI updates:

  • Updated the architecture documentation and added a new design doc to explain the three authentication states, the re-authentication flow, and the rationale for the changes. The flowchart and rationale in OIDC_PROXY_AUTH_ARCHITECTURE.md were updated for clarity. [1] [2]
  • Minor UI alignment: adjusted the login page to match the new SSO redirect path and improved layout consistency.

These changes collectively ensure a seamless and predictable authentication experience for users behind SSO, while maintaining safe behavior in unsecured deployments.

@dimetron dimetron requested a review from peterj as a code owner June 18, 2026 17:12
Copilot AI review requested due to automatic review settings June 18, 2026 17:12
@github-actions github-actions Bot added the enhancement-proposal Indicates that this PR is for an enhancement proposal label Jun 18, 2026
@dimetron dimetron force-pushed the feature/sso-session-redirect branch from 0226b0d to 54c6e05 Compare June 18, 2026 17:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the UI’s behavior when running behind oauth2-proxy by distinguishing “unsecured” (no proxy/header) from “expired” (proxy session valid but forwarded id_token expired) and triggering a guarded re-auth redirect for the expired case, instead of silently rendering a logged-out UI.

Changes:

  • Change getCurrentUser() to return an { status, user } result (authenticated | expired | unsecured) instead of CurrentUser | null.
  • Add status to AuthContext and implement a sessionStorage-based loop guard + redirect to /oauth2/start?rd=... when status is expired.
  • Add unit tests for the new auth status model and AuthContext redirect/guard behavior; update auth architecture docs and add an EP describing the design.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
ui/src/contexts/AuthContext.tsx Adds AuthStatus to context and performs guarded oauth2-proxy re-auth redirect on expired.
ui/src/contexts/tests/AuthContext.test.tsx Tests redirect/guard behavior and the new status value exposed by the provider.
ui/src/app/login/page.tsx Minor layout tweak on the login page container.
ui/src/app/actions/auth.ts Introduces AuthStatus/AuthResult and returns structured auth state from getCurrentUser().
ui/src/app/actions/tests/auth.test.ts Tests getCurrentUser()’s new { status, user } contract.
docs/OIDC_PROXY_AUTH_ARCHITECTURE.md Documents the three auth states and the re-auth flow in the auth boundary diagram/rationale.
design/EP-2045-sso-session-expiry-redirect.md Adds a design EP explaining the motivation, approach, and test plan.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ui/src/contexts/AuthContext.tsx Outdated
Comment thread ui/src/contexts/AuthContext.tsx Outdated
Comment thread ui/src/contexts/AuthContext.tsx Outdated
Comment thread ui/src/contexts/__tests__/AuthContext.test.tsx
Comment thread docs/OIDC_PROXY_AUTH_ARCHITECTURE.md Outdated
Comment thread design/EP-2045-sso-session-expiry-redirect.md
@dimetron dimetron marked this pull request as draft June 18, 2026 17:23
@dimetron dimetron changed the title Feature/sso session redirect feat(sso) expired token session redirect Jun 18, 2026
@dimetron dimetron marked this pull request as ready for review June 18, 2026 17:51
@mesutoezdil

mesutoezdil commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

3 small questions

nr1// auth.ts : when decodeJWT returns null (malformed token), the function returns status: "expired". but a malformed token is not really expired, it is invalid. would it make more sense to have a third path here so the ui does not try to reauth on a broken token?

nr2// jwt.ts:isTokenExpired returns false when claims.exp is missing, so a token with no expiry is treated as valid forever. is that intentional? feels like it could be a risk if a token without exp somehow ends up here.

nr3// AuthContext.tsx :REAUTH_GUARD_WINDOW_MS is 10 seconds. if the oidc flow at the idp takes longer than that (slow network, slow idp), the guard expires and the user hits the error path instead of completing reauth. would 60 seconds be more appropriate here?

dimetron added a commit to dimetron/kagent that referenced this pull request Jun 19, 2026
Address review feedback on PR kagent-dev#2051:
- jwt: treat a missing `exp` claim as expired instead of valid-forever, so a
  non-compliant/garbled id_token forces re-auth rather than being trusted.
- AuthContext: widen REAUTH_GUARD_WINDOW_MS 10s -> 60s so a slow IdP round-trip
  isn't misread as a failed redirect loop.
- Add jwt unit tests covering decode (valid/malformed) and isTokenExpired
  (future/past/missing exp).

Signed-off-by: Dmytro Rashko <dmitriy.rashko@amdocs.com>
@dimetron

Copy link
Copy Markdown
Contributor Author

Thanks @mesutoezdil — all three are fair points. Addressed in the latest commit:

nr2 (jwt.ts — missing exp): Agreed, valid-forever was a risk. isTokenExpired now treats a missing exp as expired. OIDC id_tokens are required to carry exp, so a token without one is non-compliant/garbled and should force re-auth rather than be trusted indefinitely.

nr3 (AuthContext.tsx — guard window): Bumped REAUTH_GUARD_WINDOW_MS from 10s → 60s to cover a slow IdP round-trip, so an in-flight re-auth isn't misread as a failed loop while still catching a fast redirect loop.

nr1 (auth.ts — malformed token → expired): Kept malformed-token handling on the expired path intentionally. Behind oauth2-proxy a malformed forwarded id_token almost always means the proxy needs to re-mint one, so re-running the OIDC flow is the correct recovery, and the sessionStorage loop-guard (now 60s) prevents thrashing if the proxy keeps forwarding a broken token (the user lands on the error path after one failed cycle instead of looping). A separate "invalid" status would behave identically to "expired" here, so I left it as one path to avoid added surface area — happy to split it out if you'd prefer the explicit state.

Also added ui/src/lib/__tests__/jwt.test.ts covering decode (valid/malformed) and isTokenExpired (future/past/missing exp).

dimetron added a commit to dimetron/kagent that referenced this pull request Jun 19, 2026
Address review feedback on PR kagent-dev#2051:
- jwt: treat a missing `exp` claim as expired instead of valid-forever, so a
  non-compliant/garbled id_token forces re-auth rather than being trusted.
- AuthContext: widen REAUTH_GUARD_WINDOW_MS 10s -> 60s so a slow IdP round-trip
  isn't misread as a failed redirect loop.
- Add jwt unit tests covering decode (valid/malformed) and isTokenExpired
  (future/past/missing exp).

Signed-off-by: Dmytro Rashko <dmitriy.rashko@amdocs.com>
@dimetron dimetron force-pushed the feature/sso-session-redirect branch from b259fca to aed3178 Compare June 19, 2026 04:47
Distinguish authenticated/expired/unsecured auth states in the UI. When behind
oauth2-proxy and the forwarded id_token has expired (but the proxy session is
still valid), re-run the OIDC flow via /oauth2/start instead of silently
rendering a logged-out UI. Never redirect in unsecured mode, and guard against
redirect loops via a sessionStorage window.

Review hardening:
- jwt: treat a missing `exp` claim as expired rather than valid-forever, so a
  non-compliant id_token forces re-auth instead of being trusted.
- AuthContext: widen the re-auth loop guard 10s -> 60s so a slow IdP round-trip
  isn't misread as a failed redirect loop.

Includes jest coverage (getCurrentUser auth states, AuthProvider redirect/guard,
jwt decode + isTokenExpired), design/EP-2045-sso-session-expiry-redirect.md, and
NEXT_PUBLIC_SSO_REDIRECT_PATH wiring in the Helm chart.

Signed-off-by: Dmytro Rashko <dmitriy.rashko@amdocs.com>
@dimetron dimetron force-pushed the feature/sso-session-redirect branch from aed3178 to 8eabd19 Compare June 19, 2026 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement-proposal Indicates that this PR is for an enhancement proposal

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants