ADR-005: Auth Strategy
Estado
Section titled “Estado”Aceptada - 2024 (implementada en frontend y backend)
Contexto
Section titled “Contexto”El sistema Nostromo requiere autenticación segura para:
- Usuarios del sistema: Empleados de empresas que usan Sevastopol (frontend) para acceder a nóminas, finiquitos, etc.
- Llamadas API: Sevastopol debe autenticarse con Orchestrator para cada request
- Multi-tenant security: Token debe identificar usuario Y tenant sin posibilidad de impersonación
Requisitos de seguridad
Section titled “Requisitos de seguridad”- Prevención XSS: Tokens NO deben ser accesibles desde JavaScript malicioso
- Prevención CSRF: Prevenir Cross-Site Request Forgery
- Stateless: Backend NO debe mantener sesiones en memoria (escalabilidad)
- Token expiration: Tokens deben expirar automáticamente
- Refresh capability: Usuarios no deben re-autenticarse cada 15 minutos
Decisión
Section titled “Decisión”Implementar JWT (JSON Web Tokens) almacenados en HTTP-only cookies:
Flujo de autenticación
Section titled “Flujo de autenticación”- Login: Usuario envía credenciales a
POST /api/command/auth/login - JWT Generation: Orchestrator valida credenciales y genera JWT firmado
- Cookie Setting: JWT se devuelve en cookie
sid(session ID) con flags:HttpOnly: NO accesible desde JavaScriptSecure: Solo transmitido en HTTPSSameSite=Strict: Previene CSRF
- Subsequent Requests: Browser envía automáticamente cookie en cada request
- JWT Validation: Middleware
authenticateTokenvalida firma y expiration
Estructura del JWT
Section titled “Estructura del JWT”{ "userId": "uuid-user-123", "tenantId": "uuid-tenant-abc", "roles": ["USER", "ADMIN"], "iat": 1705600000, "exp": 1705686400}Firma: HMAC-SHA256 con secret almacenado en variable de entorno JWT_SECRET.
Consecuencias
Section titled “Consecuencias”Positivas
Section titled “Positivas”✅ Protección contra XSS
HttpOnlyflag previene acceso desdedocument.cookie- Malware JavaScript NO puede robar token
- Si Sevastopol tiene vulnerabilidad XSS, tokens siguen protegidos
✅ Transmisión automática
- Browser envía cookie automáticamente en cada request
- No se requiere lógica manual de “attach token to headers”
- Simplifica código del frontend
✅ Stateless backend
- Orchestrator NO mantiene sesiones en memoria
- Cada nodo puede validar JWTs independientemente
- Escala horizontalmente sin session store compartido
✅ Multi-tenant security
- Token incluye
tenantIdfirmado criptográficamente - Imposible cambiar tenant sin invalidar firma
- Previene escalation attacks (usuario de empresa A accediendo a empresa B)
✅ Expiration automática
- Token tiene lifetime de 24 horas (configurable)
- Después expira automáticamente, forzando re-login
- Balance entre UX (no molestar cada hora) y seguridad
Negativas (Trade-offs)
Section titled “Negativas (Trade-offs)”❌ Vulnerabilidad CSRF moderada
- Cookies se envían automáticamente, incluyendo desde sitios maliciosos
- Mitigación:
SameSite=Strictpreviene envío cross-site - Mitigación adicional: CORS configurado estrictamente
❌ No se puede acceder desde JavaScript
- Si frontend necesita token para llamada directa (ej: WebSocket), no está disponible
- Mitigación: Para WebSockets, generar token separado vía endpoint autenticado
❌ Revocation compleja
- JWT es “self-contained” - no se puede invalidar sin blacklist
- Si usuario hace logout, token sigue válido hasta expiration
- Mitigación: Lifetime corta (24h) limita ventana de exposición
- Mitigación futura: Implementar blacklist en Redis si se requiere
❌ Cookie management en desarrollo
- Chrome/Firefox requieren HTTPS para
Securecookies - En local dev, se debe deshabilitar
Secureflag - Mitigación: Variable de entorno
NODE_ENVcontrola flags de cookie
Alternativas Consideradas
Section titled “Alternativas Consideradas”Opción A: LocalStorage + Authorization Header
Section titled “Opción A: LocalStorage + Authorization Header”Descripción: Almacenar JWT en localStorage, manualmente adjuntar en header Authorization: Bearer <token>.
Ejemplo:
const token = localStorage.getItem("jwt");fetch("/api/endpoint", { headers: { Authorization: `Bearer ${token}` },});Rechazada porque:
- Vulnerable a XSS:
localStoragees accesible desde JavaScript - Un bug XSS = robo de token = brecha total de seguridad
- Común en ataques: inyectar
<script>que lealocalStoragey envíe token a atacante
Cuándo sería válida: Si NO hay riesgo de XSS (imposible garantizar en web apps complejas), o si se requiere acceso explícito desde JS (ej: SDKs de terceros).
Opción B: Session-based auth
Section titled “Opción B: Session-based auth”Descripción: Backend mantiene sesiones en memoria/Redis, envía solo session ID en cookie.
Flujo:
- Login → Create session en Redis (sessionId: userData)
- Set cookie con sessionId
- Cada request → Lookup sesión en Redis
Rechazada porque:
- Stateful backend: Requiere Redis/Memcached compartido entre nodos Orchestrator
- Complejidad adicional: Gestionar lifecycle de sesiones (cleanup, expiration)
- Costo de infraestructura: Redis adds dependency
- No aporta beneficio: Para requisitos actuales, JWT stateless es suficiente
Cuándo sería válida: Si se requiere revocation instantánea de sesiones (ej: compliance requirement), o si JWTs se vuelven demasiado grandes (demasiados claims).
Opción C: OAuth 2.0 / OIDC
Section titled “Opción C: OAuth 2.0 / OIDC”Descripción: Delegar autenticación a proveedor externo (Google, Microsoft, Auth0).
Rechazada porque:
- Overkill para v1: Sistema es interno/privado, no multi-provider SSO
- Vendor dependency: Auth0/Okta son servicios pagos
- Complejidad: OAuth flows (authorization code, PKCE) son complejos
- No elimina cookies: OAuth también usa cookies/localStorage para tokens
Cuándo sería válida: Si clientes requieren SSO enterprise (login con Google Workspace, Azure AD), o se necesita federated identity.
Referencias
Section titled “Referencias”Implementación:
- Auth Middleware -
authenticateToken,requireRole - Login Route - Generación de JWT
- Sevastopol authFetch - Cliente autenticado
Decisiones relacionadas:
- Seguridad: Autenticación - Guía detallada de autenticación
- API Endpoints - Convenciones de API
Referencias externas:
- JWT.io - Especificación y debugging de JWTs
- OWASP: Cookie Security
- MDN: HttpOnly Cookies
Notas Adicionales
Section titled “Notas Adicionales”Configuración del token
Section titled “Configuración del token”Lifetime: 24 horas por defecto
- Configurable vía
JWT_EXPIRATIONenv var - Balance entre UX (no molestar) y seguridad (window de exposición)
Secret rotation:
JWT_SECRETNO ha rotado desde implementación inicial- Mejora futura: Implementar rotación periódica de secrets (cada 90 días)
Implementación de Refresh Tokens
Section titled “Implementación de Refresh Tokens”Estado actual: NO implementado
- Usuarios deben re-login cada 24 horas
- Aceptable para MVP
Mejora futura: Refresh token pattern
- Access token (1 hora) + Refresh token (30 días)
- Access token expira → Usar refresh token para obtener nuevo access token
- Refresh token solo válido para endpoint
/refresh
Monitoring
Section titled “Monitoring”Métricas actuales:
- Login attempts (success/failure)
- JWT validation errors (expired, invalid signature)
Mejora futura:
- Alertas si spike de validation errors (posible ataque)
- Dashboard de sesiones activas por tenant