auth flow
This commit is contained in:
parent
ecd587fd8c
commit
d882325da2
@ -1,21 +1,64 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { clearAuthCookies } from "@/lib/session";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function LoggedOut() {
|
||||
const [sessionStatus, setSessionStatus] = useState<'checking' | 'cleared' | 'error'>('checking');
|
||||
|
||||
// Clear auth cookies again on this page as an extra precaution
|
||||
useEffect(() => {
|
||||
// Run an additional cookie cleanup on this page
|
||||
clearAuthCookies();
|
||||
const checkAndClearSessions = async () => {
|
||||
try {
|
||||
// Additional browser storage clearing
|
||||
console.log('Performing complete browser storage cleanup');
|
||||
|
||||
// Clear cookies
|
||||
clearAuthCookies();
|
||||
|
||||
// Clear session storage
|
||||
try {
|
||||
sessionStorage.clear();
|
||||
console.log('Session storage cleared');
|
||||
} catch (e) {
|
||||
console.error('Error clearing session storage:', e);
|
||||
}
|
||||
|
||||
// Clear local storage items related to auth
|
||||
try {
|
||||
const authLocalStoragePrefixes = ['token', 'auth', 'session', 'keycloak', 'kc', 'oidc', 'user'];
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key) {
|
||||
const keyLower = key.toLowerCase();
|
||||
if (authLocalStoragePrefixes.some(prefix => keyLower.includes(prefix))) {
|
||||
console.log(`Clearing localStorage: ${key}`);
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('Local storage auth items cleared');
|
||||
} catch (e) {
|
||||
console.error('Error clearing localStorage:', e);
|
||||
}
|
||||
|
||||
// Double check for Keycloak specific cookies
|
||||
const keycloakCookies = ['KEYCLOAK_SESSION', 'KEYCLOAK_IDENTITY', 'KC_RESTART'];
|
||||
for (const cookieName of keycloakCookies) {
|
||||
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname}; SameSite=None; Secure;`;
|
||||
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
||||
}
|
||||
|
||||
setSessionStatus('cleared');
|
||||
} catch (error) {
|
||||
console.error('Error during session cleanup:', error);
|
||||
setSessionStatus('error');
|
||||
}
|
||||
};
|
||||
|
||||
// Also clear session storage
|
||||
try {
|
||||
sessionStorage.clear();
|
||||
} catch (e) {
|
||||
console.error('Error clearing session storage:', e);
|
||||
}
|
||||
checkAndClearSessions();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -33,15 +76,38 @@ export default function LoggedOut() {
|
||||
<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>
|
||||
|
||||
{sessionStatus === 'checking' && (
|
||||
<p className="text-white/80 mb-4">
|
||||
Verifying all sessions are terminated...
|
||||
</p>
|
||||
)}
|
||||
|
||||
{sessionStatus === 'cleared' && (
|
||||
<p className="text-white/80 mb-4">
|
||||
Your session has been completely terminated and all authentication data has been cleared.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{sessionStatus === 'error' && (
|
||||
<p className="text-white/80 mb-4">
|
||||
Your session has been terminated, but there might be some residual session data.
|
||||
For complete security, please close your browser.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="mt-6">
|
||||
<Link
|
||||
href="/signin"
|
||||
className="inline-block px-8 py-3 bg-white text-gray-800 rounded hover:bg-gray-100 transition-colors mb-4"
|
||||
>
|
||||
Sign In Again
|
||||
</Link>
|
||||
|
||||
<p className="text-white/60 text-sm mt-4">
|
||||
Note: If you're automatically signed in again, try clearing your browser cookies or restarting your browser.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -13,17 +13,21 @@ export function SignOutHandler() {
|
||||
// First, clear all auth-related cookies to ensure we break any local sessions
|
||||
clearAuthCookies();
|
||||
|
||||
// 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');
|
||||
// Get Keycloak logout URL
|
||||
if (process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER) {
|
||||
console.log('Preparing complete Keycloak logout');
|
||||
|
||||
// Create a hidden form for POST logout
|
||||
// Create a proper Keycloak logout URL with all required parameters for front-channel logout
|
||||
const keycloakBaseUrl = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER;
|
||||
const logoutEndpoint = `${keycloakBaseUrl}/protocol/openid-connect/logout`;
|
||||
|
||||
// Create form for POST logout (more reliable)
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = `${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER}/protocol/openid-connect/logout`;
|
||||
form.action = logoutEndpoint;
|
||||
|
||||
// Add the id_token_hint
|
||||
if (session.accessToken) {
|
||||
// Add id_token_hint if available
|
||||
if (session?.accessToken) {
|
||||
const tokenInput = document.createElement('input');
|
||||
tokenInput.type = 'hidden';
|
||||
tokenInput.name = 'id_token_hint';
|
||||
@ -31,24 +35,37 @@ export function SignOutHandler() {
|
||||
form.appendChild(tokenInput);
|
||||
}
|
||||
|
||||
// Add post_logout_redirect_uri pointing to a special loggedout page
|
||||
// Add client_id parameter - CRITICAL for proper logout
|
||||
const clientIdInput = document.createElement('input');
|
||||
clientIdInput.type = 'hidden';
|
||||
clientIdInput.name = 'client_id';
|
||||
clientIdInput.value = process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID || 'lab';
|
||||
form.appendChild(clientIdInput);
|
||||
|
||||
// Add post_logout_redirect_uri pointing to our logged out page
|
||||
const redirectInput = document.createElement('input');
|
||||
redirectInput.type = 'hidden';
|
||||
redirectInput.name = 'post_logout_redirect_uri';
|
||||
redirectInput.value = `${window.location.origin}/loggedout`;
|
||||
form.appendChild(redirectInput);
|
||||
|
||||
// Add logout_hint=server to explicitly request server-side session cleanup
|
||||
const logoutHintInput = document.createElement('input');
|
||||
logoutHintInput.type = 'hidden';
|
||||
logoutHintInput.name = 'logout_hint';
|
||||
logoutHintInput.value = 'server';
|
||||
form.appendChild(logoutHintInput);
|
||||
|
||||
// Append to body and submit
|
||||
document.body.appendChild(form);
|
||||
console.log('Submitting Keycloak logout form with server-side logout');
|
||||
form.submit();
|
||||
} else {
|
||||
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 = '/loggedout';
|
||||
}
|
||||
};
|
||||
@ -65,7 +82,7 @@ export function SignOutHandler() {
|
||||
<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>
|
||||
<p className="text-gray-500 mt-2">Please wait while we sign you out completely.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -94,7 +94,7 @@ export function clearAuthCookies() {
|
||||
const cookies = document.cookie.split(';');
|
||||
console.log('Clearing all auth cookies');
|
||||
|
||||
// List of known auth-related cookie prefixes
|
||||
// List of known auth-related cookie prefixes and specific cookies
|
||||
const authCookiePrefixes = [
|
||||
'next-auth.',
|
||||
'__Secure-next-auth.',
|
||||
@ -105,9 +105,25 @@ export function clearAuthCookies() {
|
||||
'OAuth_Token_Request_State',
|
||||
'OAUTH2_CLIENT_ID',
|
||||
'OAUTH2_STATE',
|
||||
'XSRF-TOKEN'
|
||||
'XSRF-TOKEN',
|
||||
'AUTH_SESSION_',
|
||||
'identity',
|
||||
'session',
|
||||
'connect.sid'
|
||||
];
|
||||
|
||||
// Specific Keycloak cookies that need to be cleared
|
||||
const specificCookies = [
|
||||
'KEYCLOAK_SESSION',
|
||||
'KEYCLOAK_IDENTITY',
|
||||
'KEYCLOAK_REMEMBER_ME',
|
||||
'KC_RESTART',
|
||||
'KEYCLOAK_SESSION_LEGACY',
|
||||
'KEYCLOAK_IDENTITY_LEGACY'
|
||||
];
|
||||
|
||||
console.log(`Processing ${cookies.length} cookies`);
|
||||
|
||||
for (const cookie of cookies) {
|
||||
const [name] = cookie.split('=');
|
||||
const trimmedName = name.trim();
|
||||
@ -115,34 +131,49 @@ export function clearAuthCookies() {
|
||||
// Check if this is an auth-related cookie
|
||||
const isAuthCookie = authCookiePrefixes.some(prefix =>
|
||||
trimmedName.startsWith(prefix)
|
||||
);
|
||||
) || specificCookies.includes(trimmedName);
|
||||
|
||||
// Also clear cookies with auth-related terms
|
||||
const containsAuthTerm =
|
||||
trimmedName.toLowerCase().includes('auth') ||
|
||||
trimmedName.toLowerCase().includes('token') ||
|
||||
trimmedName.toLowerCase().includes('session');
|
||||
trimmedName.toLowerCase().includes('session') ||
|
||||
trimmedName.toLowerCase().includes('login') ||
|
||||
trimmedName.toLowerCase().includes('id');
|
||||
|
||||
if (isAuthCookie || containsAuthTerm) {
|
||||
console.log(`Clearing cookie: ${trimmedName}`);
|
||||
|
||||
// Clear the cookie with various domain/path combinations
|
||||
// Standard path
|
||||
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
||||
// Try different combinations to ensure the cookie is cleared
|
||||
const paths = ['/', '/auth', '/realms', '/admin'];
|
||||
const domains = [
|
||||
window.location.hostname, // Exact domain
|
||||
`.${window.location.hostname}`, // Domain with leading dot
|
||||
window.location.hostname.split('.').slice(-2).join('.') // Root domain
|
||||
];
|
||||
|
||||
// Root domain
|
||||
const domain = window.location.hostname.split('.').slice(-2).join('.');
|
||||
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${domain};`;
|
||||
|
||||
// Full domain
|
||||
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`;
|
||||
// Try each combination of path and domain
|
||||
for (const path of paths) {
|
||||
// Try without domain
|
||||
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path};`;
|
||||
|
||||
// Try with SameSite and Secure attributes
|
||||
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; SameSite=None; Secure;`;
|
||||
|
||||
// Try with different domains
|
||||
for (const domain of domains) {
|
||||
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain};`;
|
||||
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain}; SameSite=None; Secure;`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear localStorage items that might be related to authentication
|
||||
try {
|
||||
const authLocalStoragePrefixes = ['token', 'auth', 'session', 'keycloak', 'kc', 'user'];
|
||||
const authLocalStoragePrefixes = ['token', 'auth', 'session', 'keycloak', 'kc', 'user', 'oidc', 'login'];
|
||||
|
||||
console.log(`Checking localStorage (${localStorage.length} items)`);
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key) {
|
||||
@ -156,4 +187,33 @@ export function clearAuthCookies() {
|
||||
} catch (e) {
|
||||
console.error('Error clearing localStorage:', e);
|
||||
}
|
||||
|
||||
// Also try to clear sessionStorage
|
||||
try {
|
||||
console.log('Clearing sessionStorage');
|
||||
sessionStorage.clear();
|
||||
} catch (e) {
|
||||
console.error('Error clearing sessionStorage:', e);
|
||||
}
|
||||
|
||||
// Check for any IndexedDB databases related to auth
|
||||
try {
|
||||
if (window.indexedDB) {
|
||||
window.indexedDB.databases().then(databases => {
|
||||
databases.forEach(db => {
|
||||
if (db.name &&
|
||||
(db.name.includes('auth') ||
|
||||
db.name.includes('keycloak') ||
|
||||
db.name.includes('token'))) {
|
||||
console.log(`Deleting IndexedDB database: ${db.name}`);
|
||||
window.indexedDB.deleteDatabase(db.name);
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error('Error accessing IndexedDB databases:', err);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error clearing IndexedDB:', e);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user