9.4 KiB
US-MGN-001-001-002: Renovar Token JWT con Refresh Token
RF Asociado: RF-MGN-001-001 Módulo: MGN-001 - Fundamentos Epic: Autenticación y Seguridad Prioridad: P0 (MVP) Story Points: 3 Sprint: Sprint 1 Estado: Ready for Development Fecha: 2025-11-24
User Story
Como usuario autenticado del sistema, Quiero renovar mi token JWT automáticamente cuando expire, Para mantener mi sesión activa sin tener que volver a ingresar mis credenciales.
Descripción Detallada
Esta user story implementa el mecanismo de renovación de tokens JWT usando refresh tokens. Cuando el JWT (8 horas de vida) expira, el frontend usa el refresh token (30 días de vida) para obtener un nuevo JWT sin requerir login. Esto mejora la experiencia del usuario al evitar logouts inesperados durante el uso del sistema.
El refresh token se almacena en la tabla auth.sessions con información del dispositivo/navegador y puede ser revocado individualmente (logout en un dispositivo específico).
Criterios de Aceptación
Escenario 1: Renovación exitosa con refresh token válido
Dado que mi JWT ha expirado pero tengo un refresh token válido, Cuando el frontend detecta el JWT expirado y envía el refresh token, Entonces el sistema genera un nuevo JWT y refresh token, los retorna al frontend, y la sesión continúa sin interrupción.
Escenario 2: Refresh token inválido o revocado
Dado que mi refresh token fue revocado o no existe en la base de datos, Cuando intento renovar mi JWT, Entonces el sistema retorna error 401 "Sesión expirada" y me redirige al login.
Escenario 3: Refresh token expirado
Dado que mi refresh token tiene más de 30 días de antigüedad, Cuando intento renovar mi JWT, Entonces el sistema retorna error 401 "Refresh token expirado" y me redirige al login.
Escenario 4: Usuario desactivado durante sesión activa
Dado que mi usuario fue desactivado mientras tengo una sesión activa, Cuando intento renovar mi JWT, Entonces el sistema retorna error 403 "Usuario desactivado" y elimina el refresh token.
Escenario 5: Renovación automática transparente
Dado que estoy trabajando en el sistema y mi JWT expira, Cuando realizo una acción que requiere autenticación, Entonces el sistema renueva el JWT automáticamente sin que yo note ninguna interrupción.
Reglas de Negocio
- RN-1: JWT expira en 8 horas, refresh token expira en 30 días.
- RN-2: Al renovar JWT, se genera NUEVO refresh token (rotación de tokens).
- RN-3: Refresh token anterior se marca como
used=truey no puede reusarse. - RN-4: Un usuario puede tener múltiples refresh tokens activos (múltiples dispositivos).
- RN-5: Al hacer logout, solo se elimina el refresh token del dispositivo actual.
- RN-6: Al cambiar contraseña, se revocan TODOS los refresh tokens del usuario.
- RN-7: Refresh tokens almacenan: user_agent, ip_address, last_used_at para auditoría.
Tareas Técnicas (Checklist de Implementación)
Backend
- Crear endpoint:
POST /api/v1/auth/refresh - Implementar método:
AuthService.refreshToken(refreshToken) - Implementar método:
AuthService.validateRefreshToken(token) - Implementar método:
AuthService.rotateRefreshToken(oldToken) - Crear tabla:
auth.sessions(refresh_token, user_id, tenant_id, user_agent, ip, expires_at, used, revoked) - Crear DTO:
RefreshTokenDtocon validación @IsJWT - Implementar lógica de rotación de tokens (invalidar anterior, generar nuevo)
- Agregar unit tests para refreshToken (6 test cases)
- Agregar integration tests para POST /auth/refresh (5 test cases)
- Documentar endpoint en Swagger
- Implementar job de limpieza de tokens expirados (cron daily)
Frontend
- Crear API client:
authApi.refresh(refreshToken) - Implementar Axios interceptor para renovación automática en 401
- Implementar lógica: detectar JWT expirado antes de request (jwt-decode)
- Almacenar refresh token en localStorage/sessionStorage (según "Recordar sesión")
- Implementar retry logic: si refresh falla, redirect a login
- Agregar tests para Axios interceptor (4 test cases)
- Implementar UI feedback durante renovación (opcional: loading bar)
- Manejar race conditions (múltiples requests simultáneos con JWT expirado)
Database
- Crear tabla:
auth.sessionscon campos mencionados - Crear índice:
idx_sessions_refresh_token(UNIQUE) - Crear índice:
idx_sessions_user_idpara listar sesiones activas por usuario - Agregar trigger: marcar
last_used_atal renovar token - Validar foreign keys: user_id → auth.users, tenant_id → auth.tenants
Mockups / Wireframes
No hay UI visible para esta funcionalidad (proceso transparente al usuario).
Feedback visual opcional:
- Durante renovación: Pequeño loading indicator en la esquina superior (opcional)
- Fallo de renovación: Modal "Su sesión ha expirado. Por favor inicie sesión nuevamente" con botón "Ir al Login"
Developer Tools (para debugging):
- En console.log: "JWT expired, refreshing token..."
- En console.log: "Token refreshed successfully"
- En console.log: "Refresh failed, redirecting to login"
Casos de Prueba (Test Scenarios)
Pruebas Funcionales
-
TC-001: Refresh exitoso
- Input: refresh token válido y no expirado
- Expected: Nuevo JWT + nuevo refresh token, anterior marcado como
used=true
-
TC-002: Refresh token inválido
- Input: refresh token que no existe en DB
- Expected: Error 401 "Invalid refresh token"
-
TC-003: Refresh token expirado
- Input: refresh token con expires_at < now()
- Expected: Error 401 "Refresh token expired"
-
TC-004: Refresh token ya usado
- Input: refresh token con used=true
- Expected: Error 401 "Refresh token already used"
-
TC-005: Usuario desactivado
- Input: refresh token válido pero user.status='inactive'
- Expected: Error 403 "User inactive", token eliminado de DB
-
TC-006: Rotación de tokens
- Input: refresh exitoso
- Expected: Anterior token marcado
used=true, nuevo token creado en sessions
-
TC-007: Múltiples dispositivos
- Input: usuario con 3 refresh tokens activos (desktop, mobile, tablet)
- Expected: Renovar en un dispositivo no afecta tokens de otros dispositivos
Pruebas No Funcionales
- Performance: Response time < 100ms (solo validación de token, sin bcrypt)
- Seguridad:
- Refresh token generado con 256 bits de entropía (crypto.randomBytes)
- Stored securely (no se expone en logs)
- HTTPS required en producción
- Concurrencia:
- Manejar race condition: si 2 requests simultáneos intentan refresh, solo 1 debe tener éxito
- Implementar lock/transaction para evitar doble rotación
Dependencias
- User Stories bloqueantes: US-MGN-001-001-001 (Login con Email y Contraseña)
- Módulos requeridos: Ninguno
- Datos maestros necesarios: Usuario autenticado con refresh token existente
Notas de Implementación
- Refresh token debe generarse con
crypto.randomBytes(64).toString('hex')(Node.js) - Implementar interceptor en Axios para renovación automática:
axios.interceptors.response.use( response => response, async error => { if (error.response?.status === 401 && !error.config._retry) { error.config._retry = true; const newToken = await authService.refresh(); error.config.headers.Authorization = `Bearer ${newToken}`; return axios(error.config); } return Promise.reject(error); } ); - Considerar implementar "sliding window" para refresh token (extender vida si se usa frecuentemente)
- Implementar endpoint
GET /auth/sessionspara listar sesiones activas del usuario - Implementar endpoint
DELETE /auth/sessions/:idpara logout de dispositivo específico
Estimación Detallada
| Tarea | Estimación |
|---|---|
| Backend Development | 2 horas |
| Frontend Development | 2 horas |
| Testing (Unit + Integration) | 1 hora |
| Code Review | 0.5 horas |
| TOTAL | 5.5 horas |
Equivalente: 3 Story Points
Definition of Done (DoD)
- Código backend implementado según ET-BACKEND-MGN-001-001
- Código frontend implementado según ET-FRONTEND-MGN-001-001
- Unit tests escritos y pasando (cobertura > 80%)
- Integration tests escritos y pasando
- Axios interceptor funcionando correctamente
- Code review completado y aprobado
- Documentación Swagger actualizada
- Job de limpieza de tokens expirados implementado
- Merge a branch
developcompletado - QA manual: probar expiración de JWT y renovación automática