From 745904cbf743eb63cdf88d187f769c0ea2b0d854 Mon Sep 17 00:00:00 2001 From: awais786 Date: Fri, 15 May 2026 18:26:37 +0500 Subject: [PATCH] fix(signout): chain oauth2-proxy and Cognito logout after Django sign-out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UserStore.signOut() previously cleared only Layer 1 (Django session via POST /auth/sign-out/) before redirecting to the portal. The two remaining auth layers — the _oauth2_proxy cookie + Redis session, and the Cognito SSO session — were left intact. The next visit to any app in the bundle silently re-authenticated as the same user, no Cognito prompt either, because the upstream SSO session was still alive. VITE_OIDC_LOGOUT_URL and VITE_OIDC_CLIENT_ID were already configured in .env, and buildOAuth2SignOutUrl() was already exported from lib/oauth2-proxy.ts — the wiring was sat there unused. After this commit, the redirect chain is: POST /auth/sign-out/ ← Layer 1 (Django) → /oauth2/sign_out?rd= ← Layer 2 (oauth2-proxy cookie + Redis) → /logout?...&logout_uri=... ← Layer 3 (Cognito SSO session) → portal URL ← lands outside ForwardAuth When OIDC env vars are not set, falls back to oauth2-proxy-only (best effort) so non-mPass deployments still see Layer 2 cleared. Cognito's allowed sign-out URLs must include the portal URL — see CLAUDE.md "Cognito requirement". Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/core/store/user/index.ts | 37 ++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/apps/web/core/store/user/index.ts b/apps/web/core/store/user/index.ts index 0dc3d6a4f29..d964c4cd973 100644 --- a/apps/web/core/store/user/index.ts +++ b/apps/web/core/store/user/index.ts @@ -16,6 +16,8 @@ import { UserPermissionStore } from "@/plane-web/store/user/permission.store"; // services import { AuthService } from "@/services/auth.service"; import { UserService } from "@/services/user.service"; +// lib +import { buildOAuth2SignOutUrl } from "@/lib/oauth2-proxy"; // stores import type { IAccountStore } from "@/store/user/account.store"; import type { IUserProfileStore } from "@/store/user/profile.store"; @@ -259,10 +261,39 @@ export class UserStore implements IUserStore { // Django session already gone (or network); still clear client state and navigate. } finally { this.store.resetOnSignOut(); - // Rewrite "." → "" so we land on the portal - // (outside ForwardAuth) instead of Plane's own root, which would silently re-auth. + + // Land on the portal (parent domain, outside ForwardAuth) so the + // browser doesn't silently re-auth against Plane's own root. const portalHost = window.location.host.replace(/^[^.]+\.(?=[^.]*\.[^.]*\.)/, ""); - window.location.href = `${window.location.protocol}//${portalHost}`; + const portalUrl = `${window.location.protocol}//${portalHost}`; + + // Chain the remaining two auth layers on top of the Django sign-out + // that just completed: + // Layer 2 — /oauth2/sign_out clears the _oauth2_proxy cookie + Redis + // session, then redirects to its `rd=` target. + // Layer 3 — Cognito /logout clears the upstream SSO session, then + // redirects to its `logout_uri`, which we point at the + // portal URL. + // + // Without this chain, only Layer 1 is cleared: oauth2-proxy + Cognito + // both retain the user's session, so the next visit to any app in the + // bundle silently re-authenticates as the same user (no password + // prompt at Cognito's hosted UI either, since the SSO session is alive). + const cognitoLogoutBase = import.meta.env.VITE_OIDC_LOGOUT_URL; + const oidcClientId = import.meta.env.VITE_OIDC_CLIENT_ID; + + if (cognitoLogoutBase && oidcClientId) { + const cognitoUrl = + `${cognitoLogoutBase}?client_id=${encodeURIComponent(oidcClientId)}` + + `&logout_uri=${encodeURIComponent(portalUrl)}`; + window.location.href = buildOAuth2SignOutUrl(cognitoUrl); + } else { + // OIDC env vars not configured. Best-effort: still clear oauth2-proxy + // so the next request re-prompts (even though Cognito's SSO session + // will outlive this until its own TTL expires or the user clicks + // "Use a different account" at the hosted UI). + window.location.href = buildOAuth2SignOutUrl(portalUrl); + } } };