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