# US-MGN-001-001-002: Renovar Token JWT con Refresh Token **RF Asociado:** [RF-MGN-001-001](../../02-modelado/requerimientos-funcionales/mgn-001/RF-MGN-001-001-autenticacion-usuarios.md) **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=true` y 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: `RefreshTokenDto` con 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.sessions` con campos mencionados - [ ] Crear índice: `idx_sessions_refresh_token` (UNIQUE) - [ ] Crear índice: `idx_sessions_user_id` para listar sesiones activas por usuario - [ ] Agregar trigger: marcar `last_used_at` al 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 1. **TC-001: Refresh exitoso** - **Input:** refresh token válido y no expirado - **Expected:** Nuevo JWT + nuevo refresh token, anterior marcado como `used=true` 2. **TC-002: Refresh token inválido** - **Input:** refresh token que no existe en DB - **Expected:** Error 401 "Invalid refresh token" 3. **TC-003: Refresh token expirado** - **Input:** refresh token con expires_at < now() - **Expected:** Error 401 "Refresh token expired" 4. **TC-004: Refresh token ya usado** - **Input:** refresh token con used=true - **Expected:** Error 401 "Refresh token already used" 5. **TC-005: Usuario desactivado** - **Input:** refresh token válido pero user.status='inactive' - **Expected:** Error 403 "User inactive", token eliminado de DB 6. **TC-006: Rotación de tokens** - **Input:** refresh exitoso - **Expected:** Anterior token marcado `used=true`, nuevo token creado en sessions 7. **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 1. **Performance:** Response time < 100ms (solo validación de token, sin bcrypt) 2. **Seguridad:** - Refresh token generado con 256 bits de entropía (crypto.randomBytes) - Stored securely (no se expone en logs) - HTTPS required en producción 3. **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: ```typescript 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/sessions` para listar sesiones activas del usuario - Implementar endpoint `DELETE /auth/sessions/:id` para 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 `develop` completado - [ ] QA manual: probar expiración de JWT y renovación automática --- ## Referencias - [RF-MGN-001-001: Autenticación de Usuarios](../../02-modelado/requerimientos-funcionales/mgn-001/RF-MGN-001-001-autenticacion-usuarios.md) - [ET-BACKEND-MGN-001-001](../../02-modelado/especificaciones-tecnicas/backend/mgn-001/ET-BACKEND-MGN-001-001-autenticacion-usuarios.md) - [ET-FRONTEND-MGN-001-001](../../02-modelado/especificaciones-tecnicas/frontend/mgn-001/ET-FRONTEND-MGN-001-001-autenticacion-usuarios.md) - [TRACEABILITY-MGN-001.yaml](../../02-modelado/trazabilidad/TRACEABILITY-MGN-001.yaml) - [Auth Schema DDL](../../02-modelado/database-design/schemas/auth-schema-ddl.sql)