Files
gsc-web-kit/src/auth/SessionExpirationGuard.tsx
Claude 85a31eb3d6 feat(auth): force re-auth on failed refresh
requireAuth() now redirects on user.error so SSR catches stale sessions
that the cookie-presence middleware can't see. New SessionExpirationGuard
(client) listens on useSession() and calls signIn("keycloak") when the
JWT carries RefreshAccessTokenError or RefreshTokenMissing. Without it,
a tab idle past the Keycloak SSO lifetime sat on a dead accessToken
until a downstream API 401'd, with no UI-level redirect.

Bumps to 0.4.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 19:44:29 +02:00

51 lines
1.8 KiB
TypeScript

"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
* `<SessionProvider>` (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<SessionUser> | 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;
}