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

265 lines
7.4 KiB
Markdown

# 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
- [x] **CA-001:** El sistema debe generar access tokens con expiracion de 15 minutos
- [x] **CA-002:** El sistema debe generar refresh tokens con expiracion de 7 dias
- [x] **CA-003:** El payload del token debe incluir: userId, tenantId, email, roles
- [x] **CA-004:** Los tokens deben firmarse con algoritmo RS256 (asymmetric keys)
- [x] **CA-005:** El sistema debe validar la firma del token en cada request
- [x] **CA-006:** El sistema debe rechazar tokens expirados con error 401
- [x] **CA-007:** El sistema debe rechazar tokens con firma invalida con error 401
- [x] **CA-008:** El sistema debe extraer el usuario del token y adjuntarlo al request
### Ejemplos de Verificacion
```gherkin
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
```json
{
"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
```bash
# Generar clave privada
openssl genrsa -out private.key 2048
# Extraer clave publica
openssl rsa -in private.key -pubout -out public.key
```
### Configuracion de Tokens
```typescript
// 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 | - | - | [ ] |