keycloak improve with build 2

This commit is contained in:
alma 2026-01-02 14:53:29 +01:00
parent 246051b4f4
commit 7a8b736241
3 changed files with 96 additions and 16 deletions

View File

@ -1,13 +1,48 @@
"use client"; "use client";
import { signIn, useSession } from "next-auth/react"; import { signIn, useSession } from "next-auth/react";
import { useEffect, useState } from "react"; import { useEffect, useState, useRef } from "react";
import { useRouter } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
export default function SignIn() { export default function SignIn() {
const { data: session, status } = useSession(); const { data: session, status } = useSession();
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams();
const [initializationStatus, setInitializationStatus] = useState<string | null>(null); const [initializationStatus, setInitializationStatus] = useState<string | null>(null);
const hasAttemptedLogin = useRef(false);
const isLogoutRedirect = useRef(false);
// Check if this is a logout redirect (from Keycloak post_logout_redirect_uri)
useEffect(() => {
// Check URL parameters or session storage for logout flag
const logoutParam = searchParams.get('logout');
const fromLogout = sessionStorage.getItem('just_logged_out');
if (logoutParam === 'true' || fromLogout === 'true') {
isLogoutRedirect.current = true;
sessionStorage.removeItem('just_logged_out');
// Clear any OAuth parameters from URL to prevent callback processing
const url = new URL(window.location.href);
const hasOAuthParams = url.searchParams.has('code') ||
url.searchParams.has('state') ||
url.searchParams.has('error');
if (hasOAuthParams) {
// Remove OAuth parameters but keep logout=true
url.searchParams.delete('code');
url.searchParams.delete('state');
url.searchParams.delete('error');
url.searchParams.delete('error_description');
url.searchParams.set('logout', 'true');
// Replace URL without OAuth params
window.history.replaceState({}, '', url.toString());
}
// Don't auto-trigger login after logout
return;
}
}, [searchParams]);
useEffect(() => { useEffect(() => {
// If user is already authenticated, redirect to home // If user is already authenticated, redirect to home
@ -16,10 +51,30 @@ export default function SignIn() {
return; return;
} }
// Only trigger Keycloak sign-in if not authenticated and not loading // Don't auto-login if this is a logout redirect or we've already attempted login
if (isLogoutRedirect.current || hasAttemptedLogin.current) {
return;
}
// Don't auto-login if status is still loading (might be processing OAuth callback)
if (status === "loading") {
return;
}
// Only trigger Keycloak sign-in if not authenticated, not loading, and not from logout
// Add a longer delay to ensure OAuth callbacks have completed
if (status === "unauthenticated") { if (status === "unauthenticated") {
// Trigger Keycloak sign-in hasAttemptedLogin.current = true;
signIn("keycloak", { callbackUrl: "/" }); // Longer delay to ensure we're not in a logout redirect flow or OAuth callback
const timer = setTimeout(() => {
// Double-check we're still unauthenticated and not in a logout flow
if (!isLogoutRedirect.current) {
// Trigger Keycloak sign-in
signIn("keycloak", { callbackUrl: "/" });
}
}, 1000);
return () => clearTimeout(timer);
} }
}, [status, session, router]); }, [status, session, router]);
@ -57,6 +112,9 @@ export default function SignIn() {
} }
}, [session]); }, [session]);
// Show logout message if coming from logout
const showLogoutMessage = isLogoutRedirect.current || searchParams.get('logout') === 'true';
return ( return (
<div <div
className="min-h-screen flex items-center justify-center" className="min-h-screen flex items-center justify-center"
@ -70,7 +128,9 @@ export default function SignIn() {
<div className="w-full max-w-md space-y-8 bg-white/90 backdrop-blur-sm p-8 rounded-xl shadow-xl"> <div className="w-full max-w-md space-y-8 bg-white/90 backdrop-blur-sm p-8 rounded-xl shadow-xl">
<div> <div>
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900"> <h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
{initializationStatus === "initializing" {showLogoutMessage
? "Vous avez été déconnecté avec succès"
: initializationStatus === "initializing"
? "Initialisation de votre espace..." ? "Initialisation de votre espace..."
: initializationStatus === "success" : initializationStatus === "success"
? "Initialisation réussie, redirection..." ? "Initialisation réussie, redirection..."
@ -78,6 +138,20 @@ export default function SignIn() {
? "Échec de l'initialisation. Veuillez réessayer." ? "Échec de l'initialisation. Veuillez réessayer."
: "Redirection vers la page de connexion..."} : "Redirection vers la page de connexion..."}
</h2> </h2>
{showLogoutMessage && (
<div className="mt-4 text-center">
<button
onClick={() => {
hasAttemptedLogin.current = false;
isLogoutRedirect.current = false;
signIn("keycloak", { callbackUrl: "/" });
}}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
Se connecter
</button>
</div>
)}
{initializationStatus === "initializing" && ( {initializationStatus === "initializing" && (
<div className="flex justify-center mt-4"> <div className="flex justify-center mt-4">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div> <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>

View File

@ -10,13 +10,16 @@ export function SignOutHandler() {
useEffect(() => { useEffect(() => {
const handleSignOut = async () => { const handleSignOut = async () => {
try { try {
// Mark that we're logging out to prevent auto-login
sessionStorage.setItem('just_logged_out', 'true');
// 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) // First, sign out from NextAuth (clears NextAuth cookies)
await signOut({ await signOut({
callbackUrl: "/signin", callbackUrl: "/signin?logout=true",
redirect: false redirect: false
}); });
@ -29,10 +32,10 @@ export function SignOutHandler() {
`${keycloakIssuer}/protocol/openid-connect/logout` `${keycloakIssuer}/protocol/openid-connect/logout`
); );
// Add required parameters // Add required parameters - include logout=true in redirect URI
keycloakLogoutUrl.searchParams.append( keycloakLogoutUrl.searchParams.append(
'post_logout_redirect_uri', 'post_logout_redirect_uri',
window.location.origin + '/signin' window.location.origin + '/signin?logout=true'
); );
keycloakLogoutUrl.searchParams.append( keycloakLogoutUrl.searchParams.append(
'id_token_hint', 'id_token_hint',
@ -43,12 +46,12 @@ export function SignOutHandler() {
window.location.href = keycloakLogoutUrl.toString(); window.location.href = 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'; window.location.href = '/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'; window.location.href = '/signin?logout=true';
} }
}; };

View File

@ -362,12 +362,15 @@ 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
sessionStorage.setItem('just_logged_out', 'true');
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) // First sign out from NextAuth (clears NextAuth cookies)
await signOut({ await signOut({
callbackUrl: '/signin', callbackUrl: '/signin?logout=true',
redirect: false redirect: false
}); });
@ -377,10 +380,10 @@ export function MainNav() {
`${keycloakIssuer}/protocol/openid-connect/logout` `${keycloakIssuer}/protocol/openid-connect/logout`
); );
// Add required parameters // Add required parameters - include logout=true in redirect URI
keycloakLogoutUrl.searchParams.append( keycloakLogoutUrl.searchParams.append(
'post_logout_redirect_uri', 'post_logout_redirect_uri',
window.location.origin + '/signin' window.location.origin + '/signin?logout=true'
); );
keycloakLogoutUrl.searchParams.append( keycloakLogoutUrl.searchParams.append(
'id_token_hint', 'id_token_hint',
@ -391,12 +394,12 @@ export function MainNav() {
window.location.href = keycloakLogoutUrl.toString(); window.location.href = 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'; window.location.href = '/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'; window.location.href = '/signin?logout=true';
} }
}} }}
> >