"use client"; import { useEffect, useRef } from "react"; import { useSession, signIn } from "next-auth/react"; import type { SessionUser } from "./types"; /** * Drop-in client component that re-triggers Keycloak sign-in when the * kit's server-side refresh flow gives up (`session.user.error` set to * `RefreshAccessTokenError` or `RefreshTokenMissing`). * * Without this, a UI tab left open past the Keycloak SSO session lifetime * holds a stale `accessToken`: the middleware sees a cookie, `requireAuth` * sees an `accessToken` string, and the user only finds out things are * broken when a downstream API returns 401. Mount this inside a * `` (typically alongside other providers in the locale * root) so the session refetch interval surfaces the error within minutes. * * Renders nothing. */ export function SessionExpirationGuard() { const { data: session } = useSession(); const error = (session?.user as Partial | undefined)?.error; // Guard against React's StrictMode double-invoke + the brief window // between calling signIn and the page navigating away. const firedRef = useRef(false); useEffect(() => { if (!error || firedRef.current) return; if ( error !== "RefreshAccessTokenError" && error !== "RefreshTokenMissing" ) { return; } firedRef.current = true; const callbackUrl = typeof window !== "undefined" ? window.location.pathname + window.location.search : "/"; // signIn() POSTs the CSRF-protected provider endpoint, which then // initiates a fresh OIDC flow; the stale session cookie is replaced // atomically when the flow completes. Keycloak's own SSO cookie may // still log the user in transparently if it hasn't also expired. void signIn("keycloak", { callbackUrl }); }, [error]); return null; }