UNPKG

sdk-simple-auth

Version:

Universal JavaScript/TypeScript authentication SDK with multi-backend support, automatic token refresh, and React integration

475 lines (390 loc) 11.7 kB
# Validación Automática de Sesión ## Problema Resuelto Este feature soluciona el problema donde: - El usuario cierra la aplicación y al volver horas después sigue apareciendo como autenticado - La sesión fue cerrada en el servidor pero el cliente no lo detecta - Los tokens de Laravel Sanctum (opacos) no pueden validarse localmente como los JWT ## Cómo Funciona El SDK ahora detecta automáticamente cuando la aplicación regresa al foreground y valida la sesión con el servidor usando el refresh token. ### Eventos del DOM Monitoreados 1. **`visibilitychange`**: Detecta cuando cambias de pestaña o minimizas la app 2. **`focus`**: Detecta cuando la ventana obtiene foco nuevamente 3. **`pageshow`**: Detecta cuando la página se muestra desde caché (back button) ### Flujo de Validación ``` Usuario abre app después de X horas ↓ SessionValidator detecta evento ↓ ¿Ha pasado suficiente tiempo de inactividad? ↓ (por defecto > 5 min) Intentar refresh token con el servidor ↓ ¿Servidor acepta el refresh token? ├─ Sí → Sesión válida, actualizar tokens └─ No → Sesión inválida, cerrar sesión local ``` ## Configuración ### Configuración Básica (Recomendada) ```typescript import { AuthSDK } from 'sdk-simple-auth'; const authSDK = new AuthSDK({ authServiceUrl: 'https://api.example.com', endpoints: { login: '/auth/login', logout: '/auth/logout', refresh: '/auth/refreshToken' }, tokenRefresh: { enabled: true, // IMPORTANTE: Debe estar habilitado bufferTime: 900, maxRetries: 3 }, // NUEVO: Configuración de validación de sesión sessionValidation: { enabled: true, // Habilitar validación automática validateOnFocus: true, // Validar cuando la ventana obtiene foco validateOnVisibility: true, // Validar cuando la página se vuelve visible maxInactivityTime: 300, // 5 minutos (en segundos) autoLogoutOnInvalid: true // Cerrar sesión automáticamente si es inválida } }, { // Callbacks para manejar eventos de sesión onSessionInvalid: () => { console.log('Sesión inválida detectada, redirigiendo a login'); window.location.href = '/login'; }, onSessionValidated: () => { console.log('Sesión validada exitosamente'); } }); ``` ### Configuración Avanzada ```typescript const authSDK = new AuthSDK({ authServiceUrl: 'https://api.example.com', tokenRefresh: { enabled: true }, sessionValidation: { enabled: true, validateOnFocus: true, validateOnVisibility: true, maxInactivityTime: 600, // 10 minutos - solo validar si ha estado inactivo más de esto autoLogoutOnInvalid: false // Manejar manualmente el logout } }, { onSessionInvalid: async () => { // Manejar de forma personalizada const shouldLogout = await confirmDialog('Tu sesión ha expirado. ¿Deseas cerrar sesión?'); if (shouldLogout) { await authSDK.logout(); router.push('/login'); } }, onSessionValidated: () => { console.log('✅ Sesión válida'); }, onError: (error) => { console.error('Error de autenticación:', error); } }); ``` ### Deshabilitar Validación Automática Si no quieres la validación automática: ```typescript const authSDK = new AuthSDK({ authServiceUrl: 'https://api.example.com', sessionValidation: { enabled: false // Deshabilitar completamente } }); ``` ## Uso en Diferentes Frameworks ### React ```typescript // useAuth.ts import { useEffect } from 'react'; import { AuthSDK } from 'sdk-simple-auth'; import { useNavigate } from 'react-router-dom'; export function useAuthSDK() { const navigate = useNavigate(); const authSDK = new AuthSDK({ authServiceUrl: process.env.REACT_APP_API_URL, tokenRefresh: { enabled: true }, sessionValidation: { enabled: true, maxInactivityTime: 300 // 5 minutos } }, { onSessionInvalid: () => { console.warn('Sesión expirada'); navigate('/login'); }, onSessionValidated: () => { console.log('Sesión válida'); } }); return authSDK; } ``` ### Vue 3 ```typescript // authSDK.ts import { ref } from 'vue'; import { AuthSDK } from 'sdk-simple-auth'; import router from './router'; export const authSDK = new AuthSDK({ authServiceUrl: import.meta.env.VITE_API_URL, sessionValidation: { enabled: true } }, { onSessionInvalid: () => { console.warn('Sesión inválida'); router.push('/login'); } }); export const isAuthenticated = ref(false); authSDK.onAuthStateChanged((state) => { isAuthenticated.value = state.isAuthenticated; }); ``` ### Angular ```typescript // auth.service.ts import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { AuthSDK } from 'sdk-simple-auth'; @Injectable({ providedIn: 'root' }) export class AuthService { private authSDK: AuthSDK; constructor(private router: Router) { this.authSDK = new AuthSDK({ authServiceUrl: environment.apiUrl, sessionValidation: { enabled: true, maxInactivityTime: 300 } }, { onSessionInvalid: () => { console.warn('Sesión expirada'); this.router.navigate(['/login']); } }); } getSDK(): AuthSDK { return this.authSDK; } } ``` ### Vanilla JavaScript ```javascript // main.js import { AuthSDK } from 'sdk-simple-auth'; const authSDK = new AuthSDK({ authServiceUrl: 'https://api.example.com', sessionValidation: { enabled: true } }, { onSessionInvalid: () => { alert('Tu sesión ha expirado. Serás redirigido al login.'); window.location.href = '/login'; } }); // Login document.getElementById('loginBtn').addEventListener('click', async () => { try { const user = await authSDK.login({ email: document.getElementById('email').value, password: document.getElementById('password').value }); console.log('Login exitoso:', user); window.location.href = '/dashboard'; } catch (error) { console.error('Error en login:', error); } }); ``` ## API Adicional ### Validación Manual Si necesitas validar la sesión manualmente: ```typescript // Validar sesión con el servidor const isValid = await authSDK.validateSession(); if (!isValid) { console.log('Sesión inválida'); await authSDK.logout(); } ``` ### Obtener Estado del Validador ```typescript // Acceder al validador (si necesitas info avanzada) const status = authSDK.sessionValidator?.getStatus(); console.log('Estado del validador:', status); // { // isListening: true, // lastActivityTime: 1234567890, // inactiveSeconds: 120 // } ``` ### Forzar Validación Inmediata ```typescript // Forzar validación sin esperar eventos const isValid = await authSDK.sessionValidator?.forceValidation(); ``` ## Compatibilidad con Backends ### Laravel Sanctum ```typescript const authSDK = new AuthSDK({ authServiceUrl: 'http://localhost:8000/api', endpoints: { login: '/login', logout: '/logout', refresh: '/refresh' // Tu endpoint de refresh en Laravel }, backend: { type: 'laravel-sanctum' }, tokenRefresh: { enabled: true }, sessionValidation: { enabled: true, maxInactivityTime: 300 } }); ``` ### Node.js/Express con JWT ```typescript const authSDK = new AuthSDK({ authServiceUrl: 'http://localhost:3000', endpoints: { login: '/auth/login', logout: '/auth/logout', refresh: '/auth/refresh' }, backend: { type: 'node-express' }, tokenRefresh: { enabled: true }, sessionValidation: { enabled: true } }); ``` ## Casos de Uso ### Escenario 1: App Móvil (PWA) Usuario usa la app, la minimiza, y vuelve horas después: ```typescript const authSDK = new AuthSDK({ authServiceUrl: 'https://api.example.com', sessionValidation: { enabled: true, maxInactivityTime: 180, // 3 minutos - menos tiempo para móvil autoLogoutOnInvalid: true } }, { onSessionInvalid: () => { // Mostrar modal amigable showModal('Tu sesión ha expirado. Por favor inicia sesión de nuevo.'); } }); ``` ### Escenario 2: Sistema de Administración Usuario deja abierta la pestaña y se va a casa: ```typescript const authSDK = new AuthSDK({ authServiceUrl: 'https://admin.example.com', sessionValidation: { enabled: true, maxInactivityTime: 900, // 15 minutos - más tiempo para admin validateOnFocus: true, validateOnVisibility: true } }, { onSessionInvalid: () => { // Guardar trabajo pendiente antes de logout saveUnsavedWork().then(() => { window.location.href = '/login?reason=session_expired'; }); } }); ``` ### Escenario 3: E-commerce Usuario navega, agrega productos al carrito, se va, y vuelve: ```typescript const authSDK = new AuthSDK({ authServiceUrl: 'https://api.store.com', sessionValidation: { enabled: true, maxInactivityTime: 600, // 10 minutos autoLogoutOnInvalid: false // No cerrar automáticamente } }, { onSessionInvalid: async () => { // Permitir navegar sin cuenta, pero mostrar aviso const cart = await getCart(); if (cart.items.length > 0) { showBanner('Tu sesión expiró. Inicia sesión para completar tu compra.'); } } }); ``` ## Preguntas Frecuentes ### ¿Por qué usa refresh token en vez de un endpoint de validación? Porque muchos backends (como Laravel Sanctum estándar) no tienen un endpoint dedicado para validar sesiones. Usar el refresh token como "ping" es una solución universal que funciona con cualquier backend que soporte refresh tokens. ### ¿Qué pasa si no tengo refresh token? Si `tokenRefresh.enabled` es `false` o no hay refresh token, la validación automática no se ejecutará. Los tokens se validarán localmente (solo JWT) o se asumirán válidos hasta que fallen en una petición real. ### ¿Afecta el rendimiento? No. Los eventos solo se disparan cuando el usuario realmente vuelve a la app, y solo se valida si ha pasado el tiempo de inactividad configurado (`maxInactivityTime`). ### ¿Funciona en todas las plataformas? Sí, funciona en: - ✅ Navegadores web modernos - ✅ PWAs - ✅ Electron - ✅ Capacitor/Cordova - ❌ Node.js server-side (no hay eventos del DOM) En entornos sin DOM, la validación automática simplemente no se activa, pero puedes usar `validateSession()` manualmente. ### ¿Puedo personalizar los callbacks? Sí, todos los callbacks son opcionales: ```typescript const authSDK = new AuthSDK(config, { onSessionInvalid: () => { /* tu lógica */ }, onSessionValidated: () => { /* tu lógica */ }, onTokenRefresh: (tokens) => { /* tu lógica */ }, onError: (error) => { /* tu lógica */ } }); ``` ## Troubleshooting ### La validación no se ejecuta 1. Verifica que `sessionValidation.enabled` sea `true` 2. Verifica que `tokenRefresh.enabled` sea `true` 3. Verifica que tengas un refresh token 4. Verifica que `maxInactivityTime` no sea muy alto ### Se valida en cada cambio de pestaña Reduce `maxInactivityTime` o desactiva `validateOnVisibility`: ```typescript sessionValidation: { enabled: true, validateOnVisibility: false, // Solo validar en focus validateOnFocus: true, maxInactivityTime: 600 // Solo si inactivo > 10 min } ``` ### Errores en consola Si ves `SessionValidator: Not a browser environment`, es normal en SSR (Next.js, Nuxt). La validación solo funciona en el cliente. ## Changelog ### v2.1.0 - ✨ Añadida validación automática de sesión - ✨ Añadidos callbacks `onSessionInvalid` y `onSessionValidated` - ✨ Añadida clase `SessionValidator` - 🐛 Corregido problema de sesiones persistentes después de horas - 🐛 Mejorado manejo de tokens Laravel Sanctum