erp-core/docs/03-requerimientos/RF-auth/RF-AUTH-003.md

262 lines
10 KiB
Markdown

# 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<TokenPair> {
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 | - | - | [ ] |