Skip to content
GitHub

ADR-005: Auth Strategy

Aceptada - 2024 (implementada en frontend y backend)


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
  • 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

Implementar JWT (JSON Web Tokens) almacenados en HTTP-only cookies:

  1. Login: Usuario envía credenciales a POST /api/command/auth/login
  2. JWT Generation: Orchestrator valida credenciales y genera JWT firmado
  3. Cookie Setting: JWT se devuelve en cookie sid (session ID) con flags:
    • HttpOnly: NO accesible desde JavaScript
    • Secure: Solo transmitido en HTTPS
    • SameSite=Strict: Previene CSRF
  4. Subsequent Requests: Browser envía automáticamente cookie en cada request
  5. JWT Validation: Middleware authenticateToken valida firma y expiration
{
"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.


Protección contra XSS

  • HttpOnly flag previene acceso desde document.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 tenantId firmado 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

Vulnerabilidad CSRF moderada

  • Cookies se envían automáticamente, incluyendo desde sitios maliciosos
  • Mitigación: SameSite=Strict previene 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 Secure cookies
  • En local dev, se debe deshabilitar Secure flag
  • Mitigación: Variable de entorno NODE_ENV controla flags de cookie

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: localStorage es accesible desde JavaScript
  • Un bug XSS = robo de token = brecha total de seguridad
  • Común en ataques: inyectar <script> que lea localStorage y 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).


Descripción: Backend mantiene sesiones en memoria/Redis, envía solo session ID en cookie.

Flujo:

  1. Login → Create session en Redis (sessionId: userData)
  2. Set cookie con sessionId
  3. 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).


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.


Implementación:

Decisiones relacionadas:

Referencias externas:


Lifetime: 24 horas por defecto

  • Configurable vía JWT_EXPIRATION env var
  • Balance entre UX (no molestar) y seguridad (window de exposición)

Secret rotation:

  • JWT_SECRET NO ha rotado desde implementación inicial
  • Mejora futura: Implementar rotación periódica de secrets (cada 90 días)

Estado actual: NO implementado

  • Usuarios deben re-login cada 24 horas
  • Aceptable para MVP

Mejora futura: Refresh token pattern

  1. Access token (1 hora) + Refresh token (30 días)
  2. Access token expira → Usar refresh token para obtener nuevo access token
  3. Refresh token solo válido para endpoint /refresh

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