265 lines
7.4 KiB
Markdown
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 | - | - | [ ] |
|