FEAT: Enhance authentication session management with configurable cookie domain and improved token refresh logic
This commit is contained in:
138
plugins/01.auth-refresh.client.ts
Normal file
138
plugins/01.auth-refresh.client.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
export default defineNuxtPlugin(() => {
|
||||
// Only run on client side
|
||||
if (import.meta.server) return
|
||||
|
||||
let refreshTimer: NodeJS.Timeout | null = null
|
||||
let isRefreshing = false
|
||||
|
||||
const scheduleTokenRefresh = (expiresAt: number) => {
|
||||
// Clear existing timer
|
||||
if (refreshTimer) {
|
||||
clearTimeout(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
|
||||
// Calculate time until refresh (refresh 2 minutes before expiry)
|
||||
const refreshBuffer = 2 * 60 * 1000 // 2 minutes in milliseconds
|
||||
const timeUntilRefresh = expiresAt - Date.now() - refreshBuffer
|
||||
|
||||
console.log('[AUTH_REFRESH] Scheduling token refresh in:', Math.max(0, timeUntilRefresh), 'ms')
|
||||
|
||||
// Only schedule if we have time left
|
||||
if (timeUntilRefresh > 0) {
|
||||
refreshTimer = setTimeout(async () => {
|
||||
if (isRefreshing) return
|
||||
|
||||
try {
|
||||
isRefreshing = true
|
||||
console.log('[AUTH_REFRESH] Attempting automatic token refresh...')
|
||||
|
||||
const response = await $fetch<{ success: boolean; expiresAt?: number }>('/api/auth/refresh', {
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
if (response.success && response.expiresAt) {
|
||||
console.log('[AUTH_REFRESH] Token refresh successful, scheduling next refresh')
|
||||
scheduleTokenRefresh(response.expiresAt)
|
||||
} else {
|
||||
console.error('[AUTH_REFRESH] Token refresh failed, redirecting to login')
|
||||
await navigateTo('/login')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AUTH_REFRESH] Token refresh error:', error)
|
||||
// If refresh fails, redirect to login
|
||||
await navigateTo('/login')
|
||||
} finally {
|
||||
isRefreshing = false
|
||||
}
|
||||
}, timeUntilRefresh)
|
||||
} else {
|
||||
// Token already expired or very close to expiry, try immediate refresh
|
||||
setTimeout(async () => {
|
||||
if (isRefreshing) return
|
||||
|
||||
try {
|
||||
isRefreshing = true
|
||||
console.log('[AUTH_REFRESH] Token expired, attempting immediate refresh...')
|
||||
|
||||
const response = await $fetch<{ success: boolean; expiresAt?: number }>('/api/auth/refresh', {
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
if (response.success && response.expiresAt) {
|
||||
console.log('[AUTH_REFRESH] Immediate refresh successful')
|
||||
scheduleTokenRefresh(response.expiresAt)
|
||||
} else {
|
||||
console.error('[AUTH_REFRESH] Immediate refresh failed, redirecting to login')
|
||||
await navigateTo('/login')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AUTH_REFRESH] Immediate refresh error:', error)
|
||||
await navigateTo('/login')
|
||||
} finally {
|
||||
isRefreshing = false
|
||||
}
|
||||
}, 100) // Small delay to avoid immediate execution
|
||||
}
|
||||
}
|
||||
|
||||
const checkAndScheduleRefresh = async () => {
|
||||
try {
|
||||
const sessionData = await $fetch<{ user: any; authenticated: boolean }>('/api/auth/session')
|
||||
|
||||
if (sessionData.authenticated) {
|
||||
// Get the session cookie to extract expiry time
|
||||
const sessionCookie = useCookie('nuxt-oidc-auth')
|
||||
|
||||
if (sessionCookie.value) {
|
||||
try {
|
||||
const parsedSession = typeof sessionCookie.value === 'string'
|
||||
? JSON.parse(sessionCookie.value)
|
||||
: sessionCookie.value
|
||||
|
||||
if (parsedSession.expiresAt) {
|
||||
console.log('[AUTH_REFRESH] Found session with expiry:', new Date(parsedSession.expiresAt))
|
||||
scheduleTokenRefresh(parsedSession.expiresAt)
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('[AUTH_REFRESH] Failed to parse session cookie:', parseError)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AUTH_REFRESH] Failed to check session:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Check authentication status and schedule refresh on plugin initialization
|
||||
onMounted(() => {
|
||||
checkAndScheduleRefresh()
|
||||
})
|
||||
|
||||
// Listen for route changes to re-check auth status
|
||||
const router = useRouter()
|
||||
router.afterEach((to) => {
|
||||
// Only check on protected routes
|
||||
if (to.meta.auth !== false) {
|
||||
checkAndScheduleRefresh()
|
||||
}
|
||||
})
|
||||
|
||||
// Listen for visibility changes to refresh when tab becomes active
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) {
|
||||
// Tab became visible, check if we need to refresh
|
||||
checkAndScheduleRefresh()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Clean up timer on plugin destruction
|
||||
onBeforeUnmount(() => {
|
||||
if (refreshTimer) {
|
||||
clearTimeout(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user