NeahStable/components/auth/signout-handler.tsx
2026-01-18 14:31:10 +01:00

113 lines
4.1 KiB
TypeScript

"use client";
import { useEffect } from "react";
import { signOut, useSession } from "next-auth/react";
import { clearAuthCookies, clearKeycloakCookies } from "@/lib/session";
export function SignOutHandler() {
const { data: session } = useSession();
useEffect(() => {
const handleSignOut = async () => {
try {
// Mark that we're logging out to prevent auto-login and refresh attempts
sessionStorage.setItem('just_logged_out', 'true');
// Also set a cookie flag that persists across redirects
document.cookie = 'logout_in_progress=true; path=/; max-age=60'; // 60 seconds
// Get Keycloak issuer from environment
const keycloakIssuer = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER;
const idToken = session?.idToken;
// Clear NextAuth cookies immediately before signOut
clearAuthCookies();
// Also attempt to clear Keycloak cookies
clearKeycloakCookies();
// Mark logout on server to force login prompt on next sign-in
try {
await fetch('/api/auth/mark-logout', {
method: 'POST',
credentials: 'include',
});
} catch (error) {
console.error('Error marking logout:', error);
// Continue even if this fails
}
// End SSO session using Admin API before signing out
// This ensures the realm-wide SSO session is cleared,
// not just the client session
try {
const ssoLogoutResponse = await fetch('/api/auth/end-sso-session', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
});
if (ssoLogoutResponse.ok) {
console.log('SSO session ended successfully');
} else {
const errorData = await ssoLogoutResponse.json().catch(() => ({}));
console.warn('Failed to end SSO session via Admin API, continuing with standard logout:', errorData);
// Continue with logout even if SSO session termination fails
}
} catch (error) {
console.error('Error ending SSO session:', error);
// Continue with logout even if SSO session termination fails
}
// Sign out from NextAuth (clears NextAuth session)
await signOut({
callbackUrl: "/signin?logout=true",
redirect: false
});
// If we have Keycloak ID token and issuer, call Keycloak logout
// Note: We don't use post_logout_redirect_uri to avoid "Invalid redirect uri" error
// Instead, we'll redirect manually after logout
if (keycloakIssuer && idToken) {
const keycloakLogoutUrl = new URL(
`${keycloakIssuer}/protocol/openid-connect/logout`
);
// Add id_token_hint to identify the session
keycloakLogoutUrl.searchParams.append(
'id_token_hint',
idToken
);
// Add kc_action=LOGOUT to ensure SSO session is cleared
keycloakLogoutUrl.searchParams.append(
'kc_action',
'LOGOUT'
);
// Use a hidden iframe to logout from Keycloak without redirect
// This avoids the "Invalid redirect uri" error
const logoutIframe = document.createElement('iframe');
logoutIframe.style.display = 'none';
logoutIframe.src = keycloakLogoutUrl.toString();
document.body.appendChild(logoutIframe);
// Wait a bit for logout to process, then redirect
setTimeout(() => {
window.location.replace('/signin?logout=true');
}, 500);
} else {
// Fallback: just redirect to signin if we don't have Keycloak info
window.location.replace('/signin?logout=true');
}
} catch (error) {
console.error('Error during sign out:', error);
// Fallback: redirect to signin on error
window.location.replace('/signin?logout=true');
}
};
handleSignOut();
}, [session]);
return null;
}