From 558f4c331519174a87bafd95b5cdacb4013fbd3b Mon Sep 17 00:00:00 2001 From: ahmedai1 Date: Thu, 4 Jun 2026 18:03:02 +0100 Subject: [PATCH] fix: follow IdP end-session redirect on admin sign-out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adminLogoutFn POSTs to LibreChat's /api/auth/logout but ignores the response body. With OPENID_USE_END_SESSION_ENDPOINT=true, that endpoint returns the IdP end_session URL in { redirect }. Because the response is discarded, only the local session cookie is cleared and the IdP session survives. With ADMIN_SSO_ONLY=true the /login page then auto-redirects back to the IdP, which silently re-authenticates the same account — a sign-out loop where the user can never sign out or switch accounts. Parse the logout response and surface redirect; Sidebar.handleLogout follows it via window.location when present, mirroring LibreChat's own AuthContext logout handling. --- src/components/Sidebar.tsx | 6 +++++- src/server/auth.ts | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 98f6d77..ce99c1e 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -61,7 +61,11 @@ export function Sidebar({ user, collapsed, onToggle }: t.SidebarProps) { const handleLogout = async () => { setIsLoggingOut(true); try { - await adminLogoutFn(); + const result = await adminLogoutFn(); + if (!result.error && result.redirect) { + window.location.href = result.redirect; + return; + } await router.invalidate(); router.navigate({ to: '/login', search: { redirect: '/' } }); } catch (error) { diff --git a/src/server/auth.ts b/src/server/auth.ts index 240d400..950d32b 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -287,17 +287,24 @@ export const requireAuthFn = createServerFn({ method: 'GET' }) }; }); +const logoutResponseSchema = z.object({ redirect: z.string().optional() }); + export const adminLogoutFn = createServerFn({ method: 'POST' }).handler(async () => { try { const session = await useAppSession(); const token = session.data.token; + let redirect: string | undefined; if (token) { try { - await fetch(`${getServerApiUrl()}/api/auth/logout`, { + const response = await fetch(`${getServerApiUrl()}/api/auth/logout`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, }); + const parsed = logoutResponseSchema.safeParse(await response.json().catch(() => ({}))); + if (parsed.success) { + redirect = parsed.data.redirect; + } } catch { // Ignore remote logout errors } @@ -305,7 +312,7 @@ export const adminLogoutFn = createServerFn({ method: 'POST' }).handler(async () await clearSession(session); - return { error: false }; + return { error: false, redirect }; } catch (error) { console.error('Admin logout error:', error); return { error: true, message: 'Logout failed' };