fix: implementar verificación correcta de sesión con Authentik
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 24s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 24s
Problema: La verificación de sesión usaba HEAD /api/music que no es confiable porque: - Los headers de Authentik solo están disponibles en el servidor - El Service Worker puede servir respuestas cacheadas - No había headers no-cache para evitar respuestas obsoletas Solución: Implementar verificación correcta basada en plantillaNuxtAuthentikProxy: 1. Nuevo endpoint /api/auth/status.get.ts - Lee headers de Authentik directamente desde el servidor - Headers no-cache para verificación en tiempo real - Retorna estado autenticado + info de usuario 2. Actualizar useAuth.ts - checkAuth() ahora usa /api/auth/status - fetchUserInfo() también usa el nuevo endpoint - Lógica simplificada y más confiable Con esto, la verificación de sesión es precisa y en tiempo real, consultando directamente los headers de Authentik en el servidor.
This commit is contained in:
@@ -16,7 +16,7 @@ let focusListener: (() => void) | null = null
|
|||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const checkAuth = async (force = false) => {
|
const checkAuth = async (force = false) => {
|
||||||
// Si ya sabemos que NO estamos autenticados, no hacer más requests
|
// Si ya sabemos que NO estamos autenticados, no hacer más requests
|
||||||
// (evita loops de CORS con Authentik redirects)
|
// (evita loops innecesarios)
|
||||||
if (!isAuthenticated.value && !force) {
|
if (!isAuthenticated.value && !force) {
|
||||||
console.log('[Auth] Skipping check - already known to be unauthenticated')
|
console.log('[Auth] Skipping check - already known to be unauthenticated')
|
||||||
return
|
return
|
||||||
@@ -32,55 +32,38 @@ export const useAuth = () => {
|
|||||||
isCheckingAuth.value = true
|
isCheckingAuth.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Intenta hacer fetch a un endpoint protegido
|
// Usar endpoint dedicado que lee headers de Authentik desde el servidor
|
||||||
const response = await fetch('/api/music', {
|
// Este endpoint tiene headers no-cache y verifica la sesión en tiempo real
|
||||||
method: 'HEAD', // Solo headers, no body
|
const response = await $fetch<{ authenticated: boolean; user: any | null; timestamp: string }>('/api/auth/status', {
|
||||||
credentials: 'include', // Include cookies for auth
|
// No usar redirect: 'error' aquí porque queremos la respuesta del servidor
|
||||||
cache: 'no-store', // No cachear la respuesta
|
// El endpoint responde con authenticated: false si no hay sesión
|
||||||
redirect: 'error', // No seguir redirects (error si Authentik intenta redirigir)
|
|
||||||
signal: AbortSignal.timeout(5000) // 5s timeout
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const wasAuthenticated = isAuthenticated.value
|
const wasAuthenticated = isAuthenticated.value
|
||||||
isAuthenticated.value = response.ok
|
isAuthenticated.value = response.authenticated
|
||||||
authChecked.value = true
|
authChecked.value = true
|
||||||
lastCheckTime.value = now
|
lastCheckTime.value = now
|
||||||
|
|
||||||
// Log cambios de estado
|
// Log cambios de estado
|
||||||
if (wasAuthenticated !== isAuthenticated.value) {
|
if (wasAuthenticated !== isAuthenticated.value) {
|
||||||
console.log('[Auth] Status changed:', isAuthenticated.value ? 'authenticated' : 'unauthenticated')
|
console.log('[Auth] Status changed:', isAuthenticated.value ? 'authenticated' : 'unauthenticated')
|
||||||
|
if (response.user) {
|
||||||
|
console.log('[Auth] User:', response.user.username)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Con redirect: 'error', cualquier redirect (302) genera "Failed to fetch"
|
console.warn('[Auth] Check failed:', error.message || error)
|
||||||
// Authentik redirige a login cuando no estás autenticado = 302
|
|
||||||
console.warn('[Auth] Check failed:', error.message)
|
|
||||||
|
|
||||||
const wasAuthenticated = isAuthenticated.value
|
const wasAuthenticated = isAuthenticated.value
|
||||||
|
|
||||||
// "Failed to fetch" con redirect: 'error' configurado = sesión expirada
|
// Si el fetch falla completamente (CORS, redirect, etc), asumir no autenticado
|
||||||
// Authentik intenta redirigir (302) pero fetch lo rechaza y genera error
|
// Esto pasa cuando Authentik redirige antes de que el endpoint pueda responder
|
||||||
if (error.message && error.message.includes('Failed to fetch')) {
|
console.log('[Auth] Fetch failed - marking as unauthenticated')
|
||||||
console.log('[Auth] Failed to fetch detected - marking as unauthenticated')
|
isAuthenticated.value = false
|
||||||
isAuthenticated.value = false
|
authChecked.value = true
|
||||||
authChecked.value = true
|
|
||||||
if (wasAuthenticated) {
|
if (wasAuthenticated) {
|
||||||
console.log('[Auth] Status changed: authenticated → unauthenticated (Authentik redirect detected)')
|
console.log('[Auth] Status changed: authenticated → unauthenticated')
|
||||||
} else {
|
|
||||||
console.log('[Auth] Confirmed unauthenticated state')
|
|
||||||
}
|
|
||||||
} else if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
|
||||||
// Otro tipo de TypeError fetch = también probablemente redirect
|
|
||||||
console.log('[Auth] TypeError fetch detected - marking as unauthenticated')
|
|
||||||
isAuthenticated.value = false
|
|
||||||
authChecked.value = true
|
|
||||||
if (wasAuthenticated) {
|
|
||||||
console.log('[Auth] Status changed: authenticated → unauthenticated (fetch error)')
|
|
||||||
} else {
|
|
||||||
console.log('[Auth] Confirmed unauthenticated state')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otro tipo de error (timeout, etc) - mantener estado actual
|
|
||||||
console.log('[Auth] Network error (not auth related), maintaining current state:', error.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastCheckTime.value = now
|
lastCheckTime.value = now
|
||||||
@@ -116,9 +99,13 @@ export const useAuth = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const info = await $fetch('/api/auth/userinfo')
|
const response = await $fetch<{ authenticated: boolean; user: any | null }>('/api/auth/status')
|
||||||
userInfo.value = info
|
if (response.authenticated && response.user) {
|
||||||
console.log('[Auth] User info loaded:', info)
|
userInfo.value = response.user
|
||||||
|
console.log('[Auth] User info loaded:', response.user)
|
||||||
|
} else {
|
||||||
|
userInfo.value = null
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[Auth] Failed to fetch user info:', error)
|
console.warn('[Auth] Failed to fetch user info:', error)
|
||||||
userInfo.value = null
|
userInfo.value = null
|
||||||
|
|||||||
43
server/api/auth/status.get.ts
Normal file
43
server/api/auth/status.get.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* API endpoint para verificar el estado de autenticación en tiempo real
|
||||||
|
* Consulta los headers inyectados por Authentik Proxy Outpost
|
||||||
|
*/
|
||||||
|
export default defineEventHandler((event) => {
|
||||||
|
// Establecer headers para prevenir caching
|
||||||
|
setResponseHeaders(event, {
|
||||||
|
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
'Expires': '0'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Leer headers de Authentik en tiempo real
|
||||||
|
const headers = getHeaders(event)
|
||||||
|
|
||||||
|
const username = headers['x-authentik-username']
|
||||||
|
const email = headers['x-authentik-email']
|
||||||
|
const name = headers['x-authentik-name']
|
||||||
|
const groups = headers['x-authentik-groups']
|
||||||
|
const uid = headers['x-authentik-uid']
|
||||||
|
|
||||||
|
// Si no hay username, no hay sesión activa en Authentik
|
||||||
|
if (!username) {
|
||||||
|
return {
|
||||||
|
authenticated: false,
|
||||||
|
user: null,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sesión activa
|
||||||
|
return {
|
||||||
|
authenticated: true,
|
||||||
|
user: {
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
groups: groups ? groups.split('|') : [],
|
||||||
|
uid
|
||||||
|
},
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user