Volver

Iberia.com — Autenticación

Frontend Engineer · 2021—
AngularAngularJSTypeScriptpostMessage APISalesforce
Problema

El área privada de Iberia.com sirve a 30 millones de pasajeros potenciales en once idiomas. Cuando llegué al proyecto, convivían Angular moderno y AngularJS de 2015 en la misma aplicación: no por descuido, sino porque una migración de esa escala no puede parar. Añadir un segundo factor OTP a ese sistema —sin romper ninguna de las once localizaciones, sin detener la migración en curso, sin alterar los flujos de pago activos— era el reto.

Restricción

La verificación OTP no podía ocurrir en el Angular principal. Por razones de contrato y arquitectura de seguridad, tenía que vivir dentro de un iframe de Salesforce alojado en el mismo dominio. El iframe no tenía acceso directo al estado de la aplicación huésped, y la aplicación huésped no podía leer el DOM del iframe.

Decisión

postMessage como contrato explícito entre mundos. El Angular exterior escucha un canal de mensajes específico; el iframe de Salesforce emite eventos tipados cuando el usuario completa o falla la verificación. Ninguna de las dos partes necesita saber cómo funciona la otra internamente —solo el contrato del mensaje importa. Eso además hizo el sistema testeable de forma independiente en cada lado.

Resultado

El flujo lleva en producción desde principios de 2025. Los once idiomas funcionan sin diferencias de comportamiento. La arquitectura de mensajes ha demostrado ser más mantenible que una integración directa: cuando Salesforce actualizó su lado, el Angular exterior no necesitó cambios.

El sistema antes de llegar

Iberia.com no es una aplicación nueva sobre una arquitectura limpia. Es un sistema que lleva décadas en producción, con millones de transacciones reales y once versiones idiomáticas sincronizadas. Cuando el equipo decidió modernizar el área privada a Angular, no podía reescribirlo todo de golpe: demasiado riesgo, demasiada complejidad, demasiados usuarios activos.

La solución fue una migración incremental: el shell en Angular moderno, partes del interior todavía en AngularJS, comunicándose entre sí a través de downgradeComponent y UpgradeModule. Un sistema que funciona correctamente, pero que impone restricciones reales sobre lo que puedes añadir y cómo.

Por qué el OTP era especialmente difícil

Añadir un segundo factor de autenticación en ese contexto tenía tres capas de complejidad:

La primera: el flujo de autenticación ya existente era complejo. Sesiones que caducan en medio de pagos, usuarios con cuentas en múltiples regiones, once localizaciones con flujos ligeramente distintos por regulación local.

La segunda: la verificación tenía que ocurrir en un iframe de Salesforce. No era una decisión técnica —era un requisito de arquitectura de seguridad y de contrato. El iframe estaba alojado en el mismo dominio, pero sigue siendo un contexto de navegación separado.

La tercera: los iframes del mismo dominio pueden leer el DOM entre sí en teoría, pero hacerlo crea un acoplamiento frágil. Si el iframe cambia su estructura interna, la aplicación huésped se rompe. Necesitábamos un contrato estable.

La decisión: postMessage como API

La solución fue tratar el iframe como un servicio con una API, no como un fragmento de DOM. El contrato funciona así:

El Angular exterior monta el iframe y establece un listener en window.addEventListener('message', ...) filtrado por origen y por un type específico del mensaje. El iframe de Salesforce, cuando el usuario completa la verificación OTP, emite window.parent.postMessage({ type: 'OTP_VERIFIED', payload: {...} }, targetOrigin). Si falla o expira, emite el tipo de error correspondiente.

El Angular exterior no sabe nada del formulario OTP interno. El iframe no sabe nada del estado de la aplicación huésped. Cada lado es testeable de forma independiente —el Angular puede mockear los eventos de mensaje, el iframe puede dispararse en un test sin el shell completo.

Lo que el refactor posterior hizo visible

Una vez estabilizado el flujo, CodeScene señaló complejidad cognitiva elevada en el componente que manejaba el estado OTP. El refactor aplicó dos patrones:

Un value object OtpCode que encapsula las reglas de validación (longitud, formato numérico, no vacío) en lugar de distribuirlas como condicionales por el componente. El componente delega: “¿es válido este código?” y recibe una respuesta, no ejecuta la lógica él mismo.

Un getter canSubmit que computa su valor en función del estado actual, en lugar de un booleano sincronizado a mano. Los estados imposibles (formulario válido pero envío en progreso, por ejemplo) se vuelven ilegales a nivel de tipos con union types, no solo convencionalmente.

El resultado: menos superficie de error, estados más fáciles de razonar, y un componente que se lee de arriba a abajo sin necesidad de rastrear mutaciones dispersas.