# RF-AUTH-003: Refresh Token y Renovacion de Sesion ## Identificacion | Campo | Valor | |-------|-------| | **ID** | RF-AUTH-003 | | **Modulo** | MGN-001 | | **Nombre Modulo** | Auth - Autenticacion | | **Prioridad** | P0 | | **Complejidad** | Media | | **Estado** | Aprobado | | **Autor** | System | | **Fecha** | 2025-12-05 | --- ## Descripcion El sistema debe permitir renovar el access token utilizando un refresh token valido, sin requerir que el usuario vuelva a ingresar sus credenciales. Esto permite mantener sesiones de larga duracion de forma segura, mientras los access tokens tienen vida corta. ### Contexto de Negocio Los access tokens tienen vida corta (15 minutos) por seguridad. Sin un mecanismo de refresh, los usuarios tendrian que re-autenticarse constantemente, lo cual afecta negativamente la experiencia de usuario. El refresh token permite mantener la sesion activa hasta 7 dias sin comprometer la seguridad. --- ## Criterios de Aceptacion - [x] **CA-001:** El sistema debe aceptar un refresh token valido y generar nuevos tokens - [x] **CA-002:** El nuevo access token debe tener los mismos claims que el original - [x] **CA-003:** El refresh token usado debe invalidarse (rotacion de tokens) - [x] **CA-004:** Se debe generar un nuevo refresh token con cada renovacion - [x] **CA-005:** El sistema debe rechazar refresh tokens expirados con error 401 - [x] **CA-006:** El sistema debe rechazar refresh tokens revocados con error 401 - [x] **CA-007:** El sistema debe detectar y prevenir reuso de refresh tokens (token replay) - [x] **CA-008:** El frontend debe renovar automaticamente antes de que expire el access token ### Ejemplos de Verificacion ```gherkin Scenario: Renovacion exitosa de tokens Given un usuario con refresh token valido When el usuario envia el refresh token a /api/v1/auth/refresh Then el sistema invalida el refresh token anterior And genera un nuevo par de tokens (access + refresh) And responde con status 200 y los nuevos tokens Scenario: Refresh token expirado Given un usuario con refresh token expirado When el usuario intenta renovar tokens Then el sistema responde con status 401 And el mensaje es "Refresh token expirado" And el usuario debe hacer login nuevamente Scenario: Deteccion de token replay (reuso) Given un refresh token que ya fue usado para renovar When alguien intenta usar ese mismo refresh token Then el sistema detecta el reuso And invalida TODOS los tokens del usuario (seguridad) And responde con status 401 And el mensaje es "Sesion comprometida, por favor inicie sesion" ``` --- ## Reglas de Negocio | ID | Regla | Validacion | |----|-------|------------| | RN-001 | Refresh token valido por 7 dias | JWT exp claim | | RN-002 | Cada refresh genera nuevo refresh token (rotacion) | Token replacement | | RN-003 | Refresh token usado se invalida inmediatamente | Marcar como usado en BD | | RN-004 | Reuso de refresh token invalida toda la familia | Revocar todos tokens del usuario | | RN-005 | Maximo 5 sesiones activas por usuario | Contador de sesiones | | RN-006 | El refresh se hace 1 minuto antes de expiracion | Frontend timer | ### Token Family (Familia de Tokens) Cada refresh token pertenece a una "familia" que se origina en un login. Si se detecta reuso de un token de esa familia, toda la familia se invalida. ``` Login -> RT1 -> RT2 -> RT3 (familia activa) ↳ RT2 reusado? -> Invalida RT1, RT2, RT3 ``` --- ## Impacto en Capas ### Database | Elemento | Accion | Descripcion | |----------|--------|-------------| | Tabla | modificar | `refresh_tokens` - agregar campos | | Columna | agregar | `family_id` UUID - familia del token | | Columna | agregar | `is_used` BOOLEAN - si ya fue usado | | Columna | agregar | `used_at` TIMESTAMPTZ - cuando se uso | | Columna | agregar | `replaced_by` UUID - token que lo reemplazo | | Indice | crear | `idx_refresh_tokens_family` | ### Backend | Elemento | Accion | Descripcion | |----------|--------|-------------| | Controller | crear | `AuthController.refresh()` | | Method | crear | `TokenService.refreshTokens()` | | Method | crear | `TokenService.detectTokenReuse()` | | Method | crear | `TokenService.revokeTokenFamily()` | | DTO | crear | `RefreshTokenDto` | | DTO | crear | `TokenResponseDto` | | Endpoint | crear | `POST /api/v1/auth/refresh` | ### Frontend | Elemento | Accion | Descripcion | |----------|--------|-------------| | Service | modificar | `tokenService.refreshTokens()` | | Interceptor | crear | Auto-refresh interceptor | | Timer | crear | Refresh timer (1 min antes de exp) | | Handler | crear | Token expiration handler | --- ## Dependencias ### Depende de (Bloqueantes) | ID | Requerimiento | Estado | |----|---------------|--------| | RF-AUTH-001 | Login | Genera tokens iniciales | | RF-AUTH-002 | JWT Tokens | Estructura de tokens | ### Dependencias Relacionadas | ID | Requerimiento | Relacion | |----|---------------|----------| | RF-AUTH-004 | Logout | Revoca refresh tokens | --- ## Especificaciones Tecnicas ### Flujo de Refresh ``` ┌─────────────────────────────────────────────────────────────────┐ │ 1. Frontend detecta que access token expira pronto │ │ (1 minuto antes de exp) │ └───────────────────────────┬─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 2. Frontend envia refresh token a POST /api/v1/auth/refresh │ └───────────────────────────┬─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 3. Backend valida refresh token │ │ - Verifica firma │ │ - Verifica expiracion │ │ - Verifica que no este usado (is_used = false) │ │ - Verifica que no este revocado │ └───────────────────────────┬─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 4. Si es valido: │ │ - Marca token actual como usado (is_used = true) │ │ - Genera nuevo access token │ │ - Genera nuevo refresh token (misma family_id) │ │ - Actualiza replaced_by del token anterior │ │ - Retorna nuevos tokens │ └───────────────────────────┬─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 5. Frontend almacena nuevos tokens │ │ - Actualiza access token en memoria/storage │ │ - Actualiza refresh token en httpOnly cookie │ │ - Reinicia timer de refresh │ └─────────────────────────────────────────────────────────────────┘ ``` ### Deteccion de Token Replay ```typescript async refreshTokens(refreshToken: string): Promise { const decoded = this.decodeToken(refreshToken); const storedToken = await this.refreshTokenRepo.findOne({ where: { jti: decoded.jti } }); // Detectar reuso if (storedToken.isUsed) { // ALERTA: Token replay detectado await this.revokeTokenFamily(storedToken.familyId); throw new UnauthorizedException('Sesion comprometida'); } // Marcar como usado storedToken.isUsed = true; storedToken.usedAt = new Date(); // Generar nuevos tokens const newTokens = await this.generateTokenPair(decoded.sub, decoded.tid); // Vincular tokens storedToken.replacedBy = newTokens.refreshTokenId; await this.refreshTokenRepo.save(storedToken); return newTokens; } ``` --- ## Datos de Prueba | Escenario | Entrada | Resultado | |-----------|---------|-----------| | Refresh exitoso | Refresh token valido | 200, nuevos tokens | | Token expirado | RT con exp < now | 401, "Token expirado" | | Token ya usado | RT con is_used = true | 401, "Sesion comprometida" | | Token revocado | RT en revoked_tokens | 401, "Token revocado" | | Sin refresh token | Body vacio | 400, "Refresh token requerido" | --- ## Estimacion | Capa | Story Points | Notas | |------|--------------|-------| | Database | 2 | Columnas adicionales en refresh_tokens | | Backend | 5 | Logica de rotacion y deteccion reuso | | Frontend | 3 | Auto-refresh interceptor y timer | | **Total** | **10** | | --- ## Notas Adicionales - El refresh token debe enviarse en httpOnly cookie, no en body (previene XSS) - Considerar sliding window: extender expiracion si hay actividad - Implementar rate limiting en endpoint de refresh (max 1 req/segundo) - Loguear todos los refreshes para auditoria - En caso de breach, proporcionar endpoint para revocar todas las sesiones --- ## Historial de Cambios | Version | Fecha | Autor | Cambios | |---------|-------|-------|---------| | 1.0 | 2025-12-05 | System | Creacion inicial | --- ## Aprobaciones | Rol | Nombre | Fecha | Firma | |-----|--------|-------|-------| | Analista | System | 2025-12-05 | [x] | | Tech Lead | - | - | [ ] | | Product Owner | - | - | [ ] |