export default defineNuxtRouteMiddleware(async (to) => { // Skip auth for SSR if (import.meta.server) return; // Check if auth is required (default true unless explicitly set to false) const isAuthRequired = to.meta.auth !== false; if (!isAuthRequired) { console.log('[MIDDLEWARE] Auth not required for route:', to.path); return; } // Skip auth check if we're already on the login page to prevent redirect loops if (to.path === '/login' || to.path.startsWith('/auth')) { return; } console.log('[MIDDLEWARE] Checking authentication for route:', to.path); // Use a cached auth state to avoid excessive API calls const nuxtApp = useNuxtApp(); const cacheKey = 'auth:session:cache'; const cacheExpiry = 5 * 60 * 1000; // 5 minutes cache (increased from 30 seconds) // Check if we have a cached session const cachedSession = nuxtApp.payload.data?.[cacheKey]; const now = Date.now(); if (cachedSession && cachedSession.timestamp && (now - cachedSession.timestamp) < cacheExpiry) { console.log('[MIDDLEWARE] Using cached session'); if (cachedSession.authenticated && cachedSession.user) { // Store auth state for components if (!nuxtApp.payload.data) { nuxtApp.payload.data = {}; } nuxtApp.payload.data.authState = { user: cachedSession.user, authenticated: cachedSession.authenticated, groups: cachedSession.groups || [] }; return; } return navigateTo('/login'); } try { // Check Keycloak authentication via session API with timeout and retries const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 10000); // 10 second timeout (increased from 5) const sessionData = await $fetch('/api/auth/session', { signal: controller.signal, retry: 2, // Increased retry count retryDelay: 1000, // Increased retry delay onRetry: ({ retries }: { retries: number }) => { console.log(`[MIDDLEWARE] Retrying auth check (attempt ${retries + 1})`) } }) as any; clearTimeout(timeout); // Cache the session data if (!nuxtApp.payload.data) { nuxtApp.payload.data = {}; } nuxtApp.payload.data[cacheKey] = { ...sessionData, timestamp: now }; // Store auth state for components nuxtApp.payload.data.authState = { user: sessionData.user, authenticated: sessionData.authenticated, groups: sessionData.groups || [] }; console.log('[MIDDLEWARE] Session check result:', { authenticated: sessionData.authenticated, hasUser: !!sessionData.user, userId: sessionData.user?.id, groups: sessionData.groups || [] }); if (sessionData.authenticated && sessionData.user) { console.log('[MIDDLEWARE] User authenticated, allowing access'); // Check for any auth errors from authorization middleware if (nuxtApp.payload.authError) { const toast = useToast(); toast.error(String(nuxtApp.payload.authError)); delete nuxtApp.payload.authError; } return; } console.log('[MIDDLEWARE] No valid authentication found, redirecting to login'); return navigateTo('/login'); } catch (error: any) { console.error('[MIDDLEWARE] Auth check failed:', error); // If it's a network error or timeout, check if we have a recent cached session if (error.name === 'AbortError' || error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') { console.log('[MIDDLEWARE] Network error, checking for recent cache'); const recentCache = nuxtApp.payload.data?.[cacheKey]; if (recentCache && recentCache.timestamp && (now - recentCache.timestamp) < 30 * 60 * 1000) { // 30 minutes grace period console.log('[MIDDLEWARE] Using recent cache despite network error (age:', Math.round((now - recentCache.timestamp) / 1000), 'seconds)'); if (recentCache.authenticated && recentCache.user) { // Store auth state for components if (!nuxtApp.payload.data) { nuxtApp.payload.data = {}; } nuxtApp.payload.data.authState = { user: recentCache.user, authenticated: recentCache.authenticated, groups: recentCache.groups || [] }; // Show a warning toast if cache is older than 10 minutes if ((now - recentCache.timestamp) > 10 * 60 * 1000) { const toast = useToast(); toast.warning('Network connectivity issue - using cached authentication'); } return; } } } return navigateTo('/login'); } });