auth flow

This commit is contained in:
alma 2025-05-02 11:01:23 +02:00
parent a171e4a713
commit ecd587fd8c
4 changed files with 133 additions and 39 deletions

7
app/loggedout/layout.tsx Normal file
View File

@ -0,0 +1,7 @@
export default function LoggedOutLayout({
children,
}: {
children: React.ReactNode;
}) {
return children;
}

49
app/loggedout/page.tsx Normal file
View File

@ -0,0 +1,49 @@
"use client";
import { useEffect } from "react";
import { clearAuthCookies } from "@/lib/session";
import Link from "next/link";
export default function LoggedOut() {
// Clear auth cookies again on this page as an extra precaution
useEffect(() => {
// Run an additional cookie cleanup on this page
clearAuthCookies();
// Also clear session storage
try {
sessionStorage.clear();
} catch (e) {
console.error('Error clearing session storage:', e);
}
}, []);
return (
<div
className="min-h-screen flex items-center justify-center"
style={{
backgroundImage: "url('/signin.jpg')",
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
}}
>
<div className="w-full max-w-md p-8 bg-black/60 backdrop-blur-sm rounded-lg shadow-xl">
<div className="text-center">
<h2 className="text-3xl font-bold text-white mb-4">
You have been logged out
</h2>
<p className="text-white/80 mb-8">
Your session has been successfully terminated and all authentication data has been cleared.
</p>
<Link
href="/signin"
className="inline-block px-8 py-3 bg-white text-gray-800 rounded hover:bg-gray-100 transition-colors"
>
Sign In Again
</Link>
</div>
</div>
</div>
);
}

View File

@ -9,15 +9,34 @@ export default function SignIn() {
const searchParams = useSearchParams();
const signedOut = searchParams.get('signedOut') === 'true';
const [isRedirecting, setIsRedirecting] = useState(false);
const [isFromLogout, setIsFromLogout] = useState(false);
// Check if we came from the loggedout page
useEffect(() => {
const referrer = document.referrer;
const isFromLoggedOutPage = referrer &&
(referrer.includes('/loggedout') || referrer.includes('/signout'));
if (isFromLoggedOutPage) {
console.log('Detected navigation from logout page, preventing auto-login');
setIsFromLogout(true);
}
}, []);
useEffect(() => {
// Only automatically sign in if not explicitly signed out
if (!signedOut && !isRedirecting) {
// Only automatically sign in if not explicitly signed out and not coming from logout
if (!signedOut && !isFromLogout && !isRedirecting && !session) {
setIsRedirecting(true);
// Trigger Keycloak sign-in
signIn("keycloak", { callbackUrl: "/" });
console.log('Triggering automatic sign-in');
// Add a small delay to avoid immediate redirect which can cause loops
const timer = setTimeout(() => {
// Trigger Keycloak sign-in
signIn("keycloak", { callbackUrl: "/" });
}, 500);
return () => clearTimeout(timer);
}
}, [signedOut, isRedirecting]);
}, [signedOut, isRedirecting, isFromLogout, session]);
useEffect(() => {
if (session?.user && !session.user.nextcloudInitialized) {
@ -34,6 +53,8 @@ export default function SignIn() {
}
}, [session]);
const showManualLoginButton = signedOut || isFromLogout || isRedirecting;
return (
<div
className="min-h-screen flex items-center justify-center"
@ -46,17 +67,20 @@ export default function SignIn() {
>
<div className="w-full max-w-md space-y-8">
<div>
{signedOut ? (
{showManualLoginButton ? (
<>
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-white">
You have been signed out
{signedOut || isFromLogout ? 'You have been signed out' : 'Welcome Back'}
</h2>
<p className="mt-2 text-center text-lg text-white/80">
Click below to sign in again
Click below to sign in
</p>
<div className="mt-6 flex justify-center">
<button
onClick={() => signIn("keycloak", { callbackUrl: "/" })}
onClick={() => {
setIsRedirecting(true);
signIn("keycloak", { callbackUrl: "/" });
}}
className="px-8 py-3 bg-white text-gray-800 rounded hover:bg-gray-100 transition-colors"
>
Sign In

View File

@ -1,7 +1,7 @@
"use client";
import { useEffect } from "react";
import { signOut, useSession } from "next-auth/react";
import { useSession } from "next-auth/react";
import { clearAuthCookies } from "@/lib/session";
export function SignOutHandler() {
@ -10,49 +10,63 @@ export function SignOutHandler() {
useEffect(() => {
const handleSignOut = async () => {
try {
// Clear all auth-related cookies
// First, clear all auth-related cookies to ensure we break any local sessions
clearAuthCookies();
// First sign out from NextAuth with redirect false
await signOut({
redirect: false
});
// Then redirect to Keycloak logout with proper parameters
if (process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER) {
const keycloakLogoutUrl = new URL(
`${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER}/protocol/openid-connect/logout`
);
// Create a temporary HTML form for direct POST logout (more reliable than redirect)
if (process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER && session?.accessToken) {
console.log('Directly calling Keycloak logout endpoint');
// Add required parameters
keycloakLogoutUrl.searchParams.append(
'post_logout_redirect_uri',
`${window.location.origin}/signin?signedOut=true`
);
// Create a hidden form for POST logout
const form = document.createElement('form');
form.method = 'POST';
form.action = `${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER}/protocol/openid-connect/logout`;
// Add id_token_hint if available
if (session?.accessToken) {
keycloakLogoutUrl.searchParams.append(
'id_token_hint',
session.accessToken
);
// Add the id_token_hint
if (session.accessToken) {
const tokenInput = document.createElement('input');
tokenInput.type = 'hidden';
tokenInput.name = 'id_token_hint';
tokenInput.value = session.accessToken;
form.appendChild(tokenInput);
}
// Redirect to Keycloak logout
window.location.href = keycloakLogoutUrl.toString();
// Add post_logout_redirect_uri pointing to a special loggedout page
const redirectInput = document.createElement('input');
redirectInput.type = 'hidden';
redirectInput.name = 'post_logout_redirect_uri';
redirectInput.value = `${window.location.origin}/loggedout`;
form.appendChild(redirectInput);
// Append to body and submit
document.body.appendChild(form);
form.submit();
} else {
// Fallback if no Keycloak issuer is configured
window.location.href = '/signin?signedOut=true';
console.log('No Keycloak configuration found, performing simple redirect');
// Fallback if no Keycloak config or session
window.location.href = '/loggedout';
}
} catch (error) {
console.error('Error during logout:', error);
// Fallback if something goes wrong
window.location.href = '/signin?signedOut=true';
window.location.href = '/loggedout';
}
};
handleSignOut();
// Add a slight delay to ensure useSession has loaded
const timer = setTimeout(() => {
handleSignOut();
}, 100);
return () => clearTimeout(timer);
}, [session]);
return null;
return (
<div className="w-full h-full flex items-center justify-center">
<div className="text-center">
<h2 className="text-2xl font-bold">Logging out...</h2>
<p className="text-gray-500 mt-2">Please wait while we sign you out.</p>
</div>
</div>
);
}