From 88f0e946fd6e4c131b395413856a624f0611b1fd Mon Sep 17 00:00:00 2001 From: alma Date: Fri, 2 Jan 2026 14:12:15 +0100 Subject: [PATCH] keycloak improve flow 2 --- app/api/auth/options.ts | 11 ++++++++++ .../auth/refresh-keycloak-session/route.ts | 15 +++++++++++++ app/components/responsive-iframe.tsx | 21 +++++++++++++++---- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/app/api/auth/options.ts b/app/api/auth/options.ts index 45e77d59..fd4586ad 100644 --- a/app/api/auth/options.ts +++ b/app/api/auth/options.ts @@ -237,6 +237,17 @@ export const authOptions: NextAuthOptions = { }; } + // If refresh failed with invalid_grant (token not active), also clear tokens + if (refreshedToken.error === "RefreshAccessTokenError" && !refreshedToken.accessToken) { + console.log("Refresh token invalid, clearing session to force re-authentication"); + return { + ...refreshedToken, + accessToken: undefined, + refreshToken: undefined, + idToken: undefined, + }; + } + return refreshedToken; }, async session({ session, token }) { diff --git a/app/api/auth/refresh-keycloak-session/route.ts b/app/api/auth/refresh-keycloak-session/route.ts index b469f35a..88da6bab 100644 --- a/app/api/auth/refresh-keycloak-session/route.ts +++ b/app/api/auth/refresh-keycloak-session/route.ts @@ -48,6 +48,21 @@ export async function GET(request: NextRequest) { if (!response.ok) { const error = await response.json().catch(() => ({})); console.error('Failed to refresh Keycloak session:', error); + + // If token is invalid (user logged out from Keycloak), return specific error + if (error.error === 'invalid_grant' || + error.error_description?.includes('Token is not active') || + error.error_description?.includes('Session not active')) { + return NextResponse.json( + { + error: 'SessionInvalidated', + message: 'Keycloak session was invalidated. Please sign in again.', + details: error + }, + { status: 401 } + ); + } + return NextResponse.json( { error: 'Failed to refresh Keycloak session', details: error }, { status: response.status } diff --git a/app/components/responsive-iframe.tsx b/app/components/responsive-iframe.tsx index e152f958..b66cd39b 100644 --- a/app/components/responsive-iframe.tsx +++ b/app/components/responsive-iframe.tsx @@ -15,11 +15,13 @@ export function ResponsiveIframe({ src, className = '', allow, style }: Responsi const { data: session } = useSession(); const [isRefreshing, setIsRefreshing] = useState(false); const [iframeSrc, setIframeSrc] = useState(''); + const [hasTriedRefresh, setHasTriedRefresh] = useState(false); // Refresh NextAuth session (which will also refresh Keycloak tokens) before loading iframe useEffect(() => { - if (!session || !src || isRefreshing) { - if (src && !isRefreshing) { + // If no session or no src, or already tried refresh, just set src + if (!session || !src || hasTriedRefresh) { + if (src) { setIframeSrc(src); } return; @@ -27,6 +29,7 @@ export function ResponsiveIframe({ src, className = '', allow, style }: Responsi const refreshSession = async () => { setIsRefreshing(true); + setHasTriedRefresh(true); try { // Call our API to refresh the Keycloak session @@ -39,19 +42,29 @@ export function ResponsiveIframe({ src, className = '', allow, style }: Responsi if (response.ok) { console.log('Session refreshed before loading iframe'); } else { + const errorData = await response.json().catch(() => ({})); + + // If session was invalidated, redirect to sign-in + if (response.status === 401 && errorData.error === 'SessionInvalidated') { + console.warn('Keycloak session invalidated, redirecting to sign-in'); + // Sign out from NextAuth and redirect + window.location.href = '/signin'; + return; + } + console.warn('Failed to refresh session, iframe may require login'); } } catch (error) { console.error('Error refreshing session:', error); } finally { - // Set iframe src after attempting refresh + // Always set iframe src after attempting refresh (or on error) setIframeSrc(src); setIsRefreshing(false); } }; refreshSession(); - }, [session, src, isRefreshing]); + }, [session, src, hasTriedRefresh]); useEffect(() => { const iframe = iframeRef.current;