RF-AUTH-002: Generacion y Validacion de JWT Tokens
Identificacion
| Campo |
Valor |
| ID |
RF-AUTH-002 |
| Modulo |
MGN-001 |
| Nombre Modulo |
Auth - Autenticacion |
| Prioridad |
P0 |
| Complejidad |
Alta |
| Estado |
Aprobado |
| Autor |
System |
| Fecha |
2025-12-05 |
Descripcion
El sistema debe implementar un mecanismo de tokens JWT (JSON Web Tokens) para autenticar y autorizar las peticiones de los usuarios. Los tokens deben contener la informacion necesaria para identificar al usuario y su tenant, permitiendo el acceso a los recursos del sistema de forma segura y stateless.
Contexto de Negocio
Los tokens JWT permiten autenticacion stateless, lo cual es esencial para:
- Escalabilidad horizontal del backend
- APIs REST verdaderamente stateless
- Soporte para multiples clientes (web, mobile, integraciones)
- Aislamiento multi-tenant mediante tenant_id en el payload
Criterios de Aceptacion
Ejemplos de Verificacion
Scenario: Validacion de token valido
Given un usuario autenticado con un access token valido
When el usuario hace una peticion a un endpoint protegido
Then el sistema valida la firma del token
And extrae el userId y tenantId del payload
And permite el acceso al recurso
Scenario: Token expirado
Given un usuario con un access token expirado
When el usuario hace una peticion a un endpoint protegido
Then el sistema responde con status 401
And el mensaje es "Token expirado"
And el header incluye WWW-Authenticate: Bearer error="token_expired"
Scenario: Token con firma invalida
Given un token modificado o con firma incorrecta
When se intenta acceder a un endpoint protegido
Then el sistema responde con status 401
And el mensaje es "Token invalido"
Reglas de Negocio
| ID |
Regla |
Validacion |
| RN-001 |
Access token expira en 15 minutos |
JWT exp claim |
| RN-002 |
Refresh token expira en 7 dias |
JWT exp claim |
| RN-003 |
Los tokens usan algoritmo RS256 |
Asymmetric signing |
| RN-004 |
El payload incluye jti (JWT ID) unico |
UUID v4 |
| RN-005 |
El issuer (iss) debe ser "erp-core" |
JWT iss claim |
| RN-006 |
El audience (aud) debe ser "erp-api" |
JWT aud claim |
| RN-007 |
El tenant_id es obligatorio (excepto superadmin) |
Validacion en middleware |
Estructura del JWT Payload
{
"sub": "user-uuid", // Subject: User ID
"tid": "tenant-uuid", // Tenant ID
"email": "user@example.com", // Email del usuario
"roles": ["admin", "user"], // Roles del usuario
"permissions": ["read", "write"], // Permisos directos
"iat": 1701792000, // Issued At
"exp": 1701792900, // Expiration (15 min)
"iss": "erp-core", // Issuer
"aud": "erp-api", // Audience
"jti": "unique-token-id" // JWT ID
}
Impacto en Capas
Database
| Elemento |
Accion |
Descripcion |
| Tabla |
crear |
refresh_tokens - almacenar refresh tokens |
| Tabla |
crear |
revoked_tokens - tokens revocados |
| Columna |
- |
refresh_tokens.token_hash (hash del token) |
| Columna |
- |
refresh_tokens.expires_at |
| Columna |
- |
refresh_tokens.device_info |
| Indice |
crear |
idx_refresh_tokens_user |
| Indice |
crear |
idx_revoked_tokens_jti |
Backend
| Elemento |
Accion |
Descripcion |
| Service |
crear |
TokenService |
| Method |
crear |
generateAccessToken() |
| Method |
crear |
generateRefreshToken() |
| Method |
crear |
validateToken() |
| Method |
crear |
decodeToken() |
| Guard |
crear |
JwtAuthGuard |
| Middleware |
crear |
TokenMiddleware |
| Config |
crear |
JWT keys (public/private) |
| Interface |
crear |
JwtPayload |
| Interface |
crear |
TokenPair |
Frontend
| Elemento |
Accion |
Descripcion |
| Service |
crear |
tokenService |
| Method |
- |
getAccessToken() |
| Method |
- |
setTokens() |
| Method |
- |
clearTokens() |
| Method |
- |
isTokenExpired() |
| Interceptor |
crear |
Axios interceptor para adjuntar token |
| Storage |
usar |
localStorage/sessionStorage para tokens |
Dependencias
Depende de (Bloqueantes)
| ID |
Requerimiento |
Estado |
| RF-AUTH-001 |
Login |
Genera los tokens |
Dependencias Relacionadas
| ID |
Requerimiento |
Relacion |
| RF-AUTH-003 |
Refresh Token |
Usa refresh token |
| RF-AUTH-004 |
Logout |
Revoca tokens |
Especificaciones Tecnicas
Generacion de Claves RS256
# Generar clave privada
openssl genrsa -out private.key 2048
# Extraer clave publica
openssl rsa -in private.key -pubout -out public.key
Configuracion de Tokens
// config/jwt.config.ts
export const jwtConfig = {
accessToken: {
algorithm: 'RS256',
expiresIn: '15m',
issuer: 'erp-core',
audience: 'erp-api',
},
refreshToken: {
algorithm: 'RS256',
expiresIn: '7d',
issuer: 'erp-core',
audience: 'erp-api',
},
};
Ejemplo de Token Generado
Header:
{
"alg": "RS256",
"typ": "JWT"
}
Payload:
{
"sub": "550e8400-e29b-41d4-a716-446655440000",
"tid": "tenant-123",
"email": "user@example.com",
"roles": ["admin"],
"iat": 1701792000,
"exp": 1701792900,
"iss": "erp-core",
"aud": "erp-api",
"jti": "abc123"
}
Datos de Prueba
| Escenario |
Token |
Resultado |
| Token valido |
JWT firmado correctamente |
200, acceso permitido |
| Token expirado |
exp < now |
401, "Token expirado" |
| Firma invalida |
Token modificado |
401, "Token invalido" |
| Sin token |
Header Authorization vacio |
401, "Token requerido" |
| Formato incorrecto |
"Bearer abc123" (no JWT) |
401, "Token malformado" |
Estimacion
| Capa |
Story Points |
Notas |
| Database |
2 |
Tablas refresh_tokens, revoked_tokens |
| Backend |
5 |
TokenService, Guards, Middleware |
| Frontend |
2 |
Token storage e interceptors |
| Total |
9 |
|
Notas Adicionales
- Las claves privadas deben almacenarse en variables de entorno o secrets manager
- Considerar rotacion de claves cada 90 dias
- Implementar JWK (JSON Web Key) endpoint para distribuir clave publica
- El refresh token NO debe almacenarse en localStorage (usar httpOnly cookie)
- Implementar token blacklist en Redis para logout inmediato
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 |
- |
- |
[ ] |