# US-MGN001-003: Renovacion Automatica de Tokens ## Identificacion | Campo | Valor | |-------|-------| | **ID** | US-MGN001-003 | | **Modulo** | MGN-001 Auth | | **Sprint** | Sprint 1 | | **Prioridad** | P0 - Critica | | **Story Points** | 8 | | **Estado** | Ready | | **Autor** | System | | **Fecha** | 2025-12-05 | --- ## Historia de Usuario **Como** usuario autenticado del sistema ERP **Quiero** que mi sesion se renueve automaticamente mientras estoy usando el sistema **Para** no tener que re-autenticarme constantemente y tener una experiencia de usuario fluida --- ## Descripcion El sistema debe renovar automaticamente los tokens de acceso antes de que expiren, utilizando el refresh token. Esto permite mantener sesiones de larga duracion (hasta 7 dias) mientras los access tokens mantienen una vida corta (15 minutos) por seguridad. ### Contexto - Access tokens expiran en 15 minutos por seguridad - Los usuarios no deben ser interrumpidos mientras trabajan - El proceso debe ser transparente para el usuario --- ## Criterios de Aceptacion ### Escenario 1: Refresh automatico exitoso ```gherkin Given un usuario autenticado trabajando en el sistema And su access token expira en menos de 1 minuto And su refresh token es valido When el frontend detecta que el token esta por expirar Then el frontend envia automaticamente POST /api/v1/auth/refresh And recibe nuevos access y refresh tokens And actualiza los tokens en memoria And la cookie httpOnly es actualizada And el usuario NO es interrumpido ``` ### Escenario 2: Refresh con token valido (manual) ```gherkin Given un usuario con refresh token valido When hace POST /api/v1/auth/refresh Then el sistema valida el refresh token And genera un nuevo par de tokens And el refresh token anterior es marcado como usado And el nuevo refresh token pertenece a la misma familia And responde con status 200 ``` ### Escenario 3: Refresh token expirado ```gherkin Given un usuario con refresh token expirado (>7 dias) When intenta renovar tokens Then el sistema responde con status 401 And el mensaje es "Refresh token expirado" And el usuario es redirigido al login ``` ### Escenario 4: Deteccion de token replay ```gherkin Given un refresh token que ya fue usado para renovar And el sistema genero un nuevo token despues de usarlo When alguien intenta usar el refresh token viejo Then el sistema detecta el reuso And invalida TODA la familia de tokens And responde con status 401 And el mensaje es "Sesion comprometida. Por favor inicia sesion." And todas las sesiones del usuario son cerradas ``` ### Escenario 5: Refresh sin token ```gherkin Given un request sin refresh token When intenta renovar tokens Then el sistema responde con status 400 And el mensaje es "Refresh token requerido" ``` ### Escenario 6: Multiples requests simultaneos ```gherkin Given un usuario con token por expirar And el frontend hace 3 requests simultaneos de refresh When los requests llegan al servidor Then solo el primero obtiene nuevos tokens And los siguientes reciben los nuevos tokens (idempotente) OR los siguientes reciben error y deben reintentar con el nuevo token ``` --- ## Mockup / Wireframe ``` El proceso es INVISIBLE para el usuario. No hay UI directa. ┌──────────────────────────────────────────────────────────────────┐ │ Indicador de sesion (opcional en header) │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ [🔒 Sesion activa] ← Verde: sesion renovada │ │ │ │ [⚠️ Renovando...] ← Amarillo: refresh en progreso │ │ │ │ [❌ Sesion expirada] ← Rojo: necesita login │ │ │ └──────────────────────────────────────────────────────────────────┘ Flujo de sesion expirada: ┌──────────────────────────────────────────────────────────────────┐ │ SESION EXPIRADA │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ Tu sesion ha expirado. Por favor inicia sesion nuevamente. │ │ │ │ [ Ir a Login ] │ │ │ └──────────────────────────────────────────────────────────────────┘ ``` --- ## Notas Tecnicas ### API ```typescript // Request POST /api/v1/auth/refresh Cookie: refresh_token=eyJhbGciOiJSUzI1NiIs... // O en body (fallback) { "refreshToken": "eyJhbGciOiJSUzI1NiIs..." } // Response 200 { "accessToken": "eyJhbGciOiJSUzI1NiIs...", "refreshToken": "eyJhbGciOiJSUzI1NiIs...", "tokenType": "Bearer", "expiresIn": 900 } // Set-Cookie: refresh_token=nuevo...; HttpOnly; Secure; SameSite=Strict ``` ### Logica de Refresh en Frontend ```typescript // Token refresh interceptor (pseudo-code) class TokenRefreshService { private refreshing = false; private refreshPromise: Promise | null = null; async checkAndRefresh(): Promise { const accessToken = this.getAccessToken(); const expiresAt = this.decodeExpiration(accessToken); const now = Date.now(); const oneMinute = 60 * 1000; // Renovar si expira en menos de 1 minuto if (expiresAt - now < oneMinute) { await this.refresh(); } } async refresh(): Promise { // Evitar multiples refreshes simultaneos if (this.refreshing) { return this.refreshPromise; } this.refreshing = true; this.refreshPromise = this.doRefresh(); try { await this.refreshPromise; } finally { this.refreshing = false; this.refreshPromise = null; } } private async doRefresh(): Promise { try { const response = await api.post('/auth/refresh'); this.setTokens(response.data); } catch (error) { // Refresh failed, redirect to login this.clearTokens(); router.push('/login'); throw error; } } } ``` ### Rotacion de Tokens (Token Family) ``` Login exitoso: └── RT1 (family: ABC) ← Activo Refresh 1: ├── RT1 (family: ABC) → isUsed: true, replacedBy: RT2 └── RT2 (family: ABC) ← Activo Refresh 2: ├── RT1 (family: ABC) → isUsed: true, replacedBy: RT2 ├── RT2 (family: ABC) → isUsed: true, replacedBy: RT3 └── RT3 (family: ABC) ← Activo Token Replay (RT1 reutilizado): ├── RT1 (family: ABC) → ALERTA! ya esta usado ├── RT2 (family: ABC) → REVOCADO por seguridad └── RT3 (family: ABC) → REVOCADO por seguridad ``` --- ## Definicion de Done - [ ] Endpoint POST /api/v1/auth/refresh implementado - [ ] Rotacion de tokens funcionando - [ ] Deteccion de token replay - [ ] Revocacion de familia en caso de reuso - [ ] Cookie actualizada en cada refresh - [ ] Frontend: interceptor de refresh automatico - [ ] Frontend: manejo de sesion expirada - [ ] Tests unitarios (>80% coverage) - [ ] Tests e2e pasando - [ ] Code review aprobado --- ## Dependencias ### Requiere | Item | Descripcion | |------|-------------| | US-MGN001-001 | Login (obtener tokens iniciales) | | Tabla refresh_tokens | Con campos family_id, is_used, replaced_by | | Redis | Para rate limiting | ### Bloquea | Item | Descripcion | |------|-------------| | Todas las features | Dependen de sesion activa | --- ## Estimacion | Tarea | Horas | |-------|-------| | Backend: refreshTokens() | 4h | | Backend: Token rotation logic | 3h | | Backend: Token replay detection | 2h | | Backend: Tests | 3h | | Frontend: Refresh interceptor | 3h | | Frontend: Token storage | 2h | | Frontend: Tests | 2h | | **Total** | **19h** | --- ## Referencias - [RF-AUTH-003](../../01-requerimientos/RF-auth/RF-AUTH-003.md) - Requerimiento funcional - [RF-AUTH-002](../../01-requerimientos/RF-auth/RF-AUTH-002.md) - Estructura de tokens - [ET-auth-backend](../../02-modelado/especificaciones-tecnicas/ET-auth-backend.md) - Spec tecnica --- ## Historial | Version | Fecha | Autor | Cambios | |---------|-------|-------|---------| | 1.0 | 2025-12-05 | System | Creacion inicial |