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
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)
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
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
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
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
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
// 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
// Token refresh interceptor (pseudo-code)
class TokenRefreshService {
private refreshing = false;
private refreshPromise: Promise<void> | null = null;
async checkAndRefresh(): Promise<void> {
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<void> {
// 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<void> {
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
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
Historial
| Version |
Fecha |
Autor |
Cambios |
| 1.0 |
2025-12-05 |
System |
Creacion inicial |