keycloak improve with build 3

This commit is contained in:
alma 2026-01-02 15:30:18 +01:00
parent 7a8b736241
commit ff7e022ee7
5 changed files with 73 additions and 17 deletions

View File

@ -8,6 +8,19 @@ import { authOptions } from '../options';
*/ */
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
try { 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); const session = await getServerSession(authOptions);
if (!session?.accessToken || !session?.refreshToken) { if (!session?.accessToken || !session?.refreshToken) {

View File

@ -24,6 +24,16 @@ export function ResponsiveIframe({ src, className = '', allow, style }: Responsi
return; 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 no session yet, wait for it (don't set src yet)
if (!session) { if (!session) {
return; return;
@ -52,6 +62,14 @@ export function ResponsiveIframe({ src, className = '', allow, style }: Responsi
// Wait a bit to ensure NextAuth session is fully established // Wait a bit to ensure NextAuth session is fully established
await new Promise(resolve => setTimeout(resolve, 100)); 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 // Call our API to refresh the Keycloak session
// This ensures tokens are fresh and may help refresh Keycloak session cookies // This ensures tokens are fresh and may help refresh Keycloak session cookies
const response = await fetch('/api/auth/refresh-keycloak-session', { const response = await fetch('/api/auth/refresh-keycloak-session', {

View File

@ -14,12 +14,28 @@ export default function Home() {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { 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") { if (status !== "loading") {
setIsLoading(false); setIsLoading(false);
} }
}, [status]); }, [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 ( return (
<main className="h-screen flex items-center justify-center"> <main className="h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div> <div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div>

View File

@ -10,22 +10,24 @@ export function SignOutHandler() {
useEffect(() => { useEffect(() => {
const handleSignOut = async () => { const handleSignOut = async () => {
try { 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'); 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 // Get Keycloak issuer from environment
const keycloakIssuer = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER; const keycloakIssuer = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER;
const idToken = session?.idToken; 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({ await signOut({
callbackUrl: "/signin?logout=true", callbackUrl: "/signin?logout=true",
redirect: false redirect: false
}); });
// Clear NextAuth cookies client-side
clearAuthCookies();
// If we have Keycloak ID token and issuer, call Keycloak logout // If we have Keycloak ID token and issuer, call Keycloak logout
if (keycloakIssuer && idToken) { if (keycloakIssuer && idToken) {
const keycloakLogoutUrl = new URL( const keycloakLogoutUrl = new URL(
@ -42,16 +44,16 @@ export function SignOutHandler() {
idToken idToken
); );
// Redirect to Keycloak logout (this will clear Keycloak cookies) // Immediate redirect to Keycloak logout (prevents widget rendering)
window.location.href = keycloakLogoutUrl.toString(); window.location.replace(keycloakLogoutUrl.toString());
} else { } else {
// Fallback: just redirect to signin if we don't have Keycloak info // 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) { } catch (error) {
console.error('Error during sign out:', error); console.error('Error during sign out:', error);
// Fallback: redirect to signin on error // Fallback: redirect to signin on error
window.location.href = '/signin?logout=true'; window.location.replace('/signin?logout=true');
} }
}; };

View File

@ -28,6 +28,7 @@ import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { Sidebar } from "./sidebar"; import { Sidebar } from "./sidebar";
import { useSession, signIn, signOut } from "next-auth/react"; import { useSession, signIn, signOut } from "next-auth/react";
import { clearAuthCookies } from "@/lib/session";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@ -362,19 +363,25 @@ export function MainNav() {
className="text-white/80 hover:text-white hover:bg-black/50 cursor-pointer" className="text-white/80 hover:text-white hover:bg-black/50 cursor-pointer"
onClick={async () => { onClick={async () => {
try { 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'); 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 keycloakIssuer = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER;
const idToken = session?.idToken; 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({ await signOut({
callbackUrl: '/signin?logout=true', callbackUrl: '/signin?logout=true',
redirect: false 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) { if (keycloakIssuer && idToken) {
const keycloakLogoutUrl = new URL( const keycloakLogoutUrl = new URL(
`${keycloakIssuer}/protocol/openid-connect/logout` `${keycloakIssuer}/protocol/openid-connect/logout`
@ -390,16 +397,16 @@ export function MainNav() {
idToken idToken
); );
// Redirect to Keycloak logout (this will clear Keycloak cookies) // Immediate redirect to Keycloak logout (prevents widget rendering)
window.location.href = keycloakLogoutUrl.toString(); window.location.replace(keycloakLogoutUrl.toString());
} else { } else {
// Fallback: just redirect to signin if we don't have Keycloak info // 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) { } catch (error) {
console.error('Error during logout:', error); console.error('Error during logout:', error);
// Fallback to simple redirect if something goes wrong // Fallback to simple redirect if something goes wrong
window.location.href = '/signin?logout=true'; window.location.replace('/signin?logout=true');
} }
}} }}
> >