diff --git a/app/api/auth/refresh-keycloak-session/route.ts b/app/api/auth/refresh-keycloak-session/route.ts index 88da6bab..9eede168 100644 --- a/app/api/auth/refresh-keycloak-session/route.ts +++ b/app/api/auth/refresh-keycloak-session/route.ts @@ -8,6 +8,19 @@ import { authOptions } from '../options'; */ export async function GET(request: NextRequest) { try { + // Check if user just logged out (prevent refresh after logout) + const logoutCookie = request.cookies.get('logout_in_progress'); + if (logoutCookie?.value === 'true') { + console.log('Logout in progress, refusing to refresh session'); + return NextResponse.json( + { + error: 'SessionInvalidated', + message: 'User is logging out. Please sign in again.' + }, + { status: 401 } + ); + } + const session = await getServerSession(authOptions); if (!session?.accessToken || !session?.refreshToken) { diff --git a/app/components/responsive-iframe.tsx b/app/components/responsive-iframe.tsx index ed20ad62..77299378 100644 --- a/app/components/responsive-iframe.tsx +++ b/app/components/responsive-iframe.tsx @@ -24,6 +24,16 @@ export function ResponsiveIframe({ src, className = '', allow, style }: Responsi return; } + // Check if user just logged out - prevent refresh if logout is in progress + const justLoggedOut = sessionStorage.getItem('just_logged_out') === 'true'; + const logoutCookie = document.cookie.split(';').some(c => c.trim().startsWith('logout_in_progress=true')); + + if (justLoggedOut || logoutCookie) { + console.warn('Logout in progress, redirecting to sign-in instead of refreshing session'); + window.location.href = '/signin'; + return; + } + // If no session yet, wait for it (don't set src yet) if (!session) { return; @@ -52,6 +62,14 @@ export function ResponsiveIframe({ src, className = '', allow, style }: Responsi // Wait a bit to ensure NextAuth session is fully established await new Promise(resolve => setTimeout(resolve, 100)); + // Double-check logout flag before making the request + const stillLoggedOut = sessionStorage.getItem('just_logged_out') === 'true'; + if (stillLoggedOut) { + console.warn('Logout detected during refresh, aborting'); + window.location.href = '/signin'; + return; + } + // Call our API to refresh the Keycloak session // This ensures tokens are fresh and may help refresh Keycloak session cookies const response = await fetch('/api/auth/refresh-keycloak-session', { diff --git a/app/page.tsx b/app/page.tsx index 2bf7c383..355f8676 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -14,12 +14,28 @@ export default function Home() { const [isLoading, setIsLoading] = useState(true); useEffect(() => { + // Check if logout is in progress - if so, redirect immediately + const justLoggedOut = sessionStorage.getItem('just_logged_out') === 'true'; + const logoutCookie = document.cookie.split(';').some(c => c.trim().startsWith('logout_in_progress=true')); + + if (justLoggedOut || logoutCookie) { + // Clear the flags and redirect + sessionStorage.removeItem('just_logged_out'); + document.cookie = 'logout_in_progress=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC'; + window.location.href = '/signin?logout=true'; + return; + } + if (status !== "loading") { setIsLoading(false); } }, [status]); - if (isLoading) { + // Don't render widgets if logout is in progress + const justLoggedOut = sessionStorage.getItem('just_logged_out') === 'true'; + const logoutCookie = document.cookie.split(';').some(c => c.trim().startsWith('logout_in_progress=true')); + + if (isLoading || justLoggedOut || logoutCookie) { return (
diff --git a/components/auth/signout-handler.tsx b/components/auth/signout-handler.tsx index 43dfc055..478b9925 100644 --- a/components/auth/signout-handler.tsx +++ b/components/auth/signout-handler.tsx @@ -10,22 +10,24 @@ export function SignOutHandler() { useEffect(() => { const handleSignOut = async () => { try { - // Mark that we're logging out to prevent auto-login + // 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; - // First, sign out from NextAuth (clears NextAuth cookies) + // Clear NextAuth cookies immediately before signOut + clearAuthCookies(); + + // Sign out from NextAuth (clears NextAuth session) await signOut({ callbackUrl: "/signin?logout=true", redirect: false }); - // Clear NextAuth cookies client-side - clearAuthCookies(); - // If we have Keycloak ID token and issuer, call Keycloak logout if (keycloakIssuer && idToken) { const keycloakLogoutUrl = new URL( @@ -42,16 +44,16 @@ export function SignOutHandler() { idToken ); - // Redirect to Keycloak logout (this will clear Keycloak cookies) - window.location.href = keycloakLogoutUrl.toString(); + // Immediate redirect to Keycloak logout (prevents widget rendering) + window.location.replace(keycloakLogoutUrl.toString()); } else { // Fallback: just redirect to signin if we don't have Keycloak info - window.location.href = '/signin?logout=true'; + window.location.replace('/signin?logout=true'); } } catch (error) { console.error('Error during sign out:', error); // Fallback: redirect to signin on error - window.location.href = '/signin?logout=true'; + window.location.replace('/signin?logout=true'); } }; diff --git a/components/main-nav.tsx b/components/main-nav.tsx index acdde8a1..b1b7af76 100644 --- a/components/main-nav.tsx +++ b/components/main-nav.tsx @@ -28,6 +28,7 @@ import Image from "next/image"; import Link from "next/link"; import { Sidebar } from "./sidebar"; import { useSession, signIn, signOut } from "next-auth/react"; +import { clearAuthCookies } from "@/lib/session"; import { DropdownMenu, DropdownMenuContent, @@ -362,19 +363,25 @@ export function MainNav() { className="text-white/80 hover:text-white hover:bg-black/50 cursor-pointer" onClick={async () => { try { - // Mark that we're logging out to prevent auto-login + // 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 const keycloakIssuer = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER; const idToken = session?.idToken; - // First sign out from NextAuth (clears NextAuth cookies) + // Clear NextAuth cookies immediately before signOut + clearAuthCookies(); + + // 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 + // Force immediate redirect to prevent widgets from rendering + // This ensures session state is cleared before any components re-render if (keycloakIssuer && idToken) { const keycloakLogoutUrl = new URL( `${keycloakIssuer}/protocol/openid-connect/logout` @@ -390,16 +397,16 @@ export function MainNav() { idToken ); - // Redirect to Keycloak logout (this will clear Keycloak cookies) - window.location.href = keycloakLogoutUrl.toString(); + // Immediate redirect to Keycloak logout (prevents widget rendering) + window.location.replace(keycloakLogoutUrl.toString()); } else { // Fallback: just redirect to signin if we don't have Keycloak info - window.location.href = '/signin?logout=true'; + window.location.replace('/signin?logout=true'); } } catch (error) { console.error('Error during logout:', error); // Fallback to simple redirect if something goes wrong - window.location.href = '/signin?logout=true'; + window.location.replace('/signin?logout=true'); } }} >