fix: implementar verificación correcta de sesión con Authentik
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:
2025-10-17 04:09:28 -06:00
parent d9795f6752
commit 01f7a0fd2a
2 changed files with 69 additions and 39 deletions

View File

@@ -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

View 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()
}
})