Para salvaguardar las cuentas de nuestros usuarios y evitar el fraude, a veces pedimos a los usuarios que verifiquen su identidad o confirmen una transacción completando una "fricción de usuario", como la autenticación de dos factores. Las fricciones del usuario, o pasos de verificación diseñados para prevenir el fraude, son esenciales para combatir la actividad fraudulenta, pero no siempre son fáciles de implementar rápidamente. En DoorDash, a veces nos encontramos con que nuestro conjunto de aplicaciones es objeto de abusos y ataques debido a lagunas imprevistas. En tales situaciones, la introducción de una función de fricción en esos flujos de trabajo resulta eficaz para evitar daños mayores.
Nuestra implementación inicial de fricciones se centró únicamente en la aplicación para consumidores de DoorDash. Si bien fue eficaz a corto plazo, pronto nos dimos cuenta de que los casos de uso se expandían más allá de nuestra aplicación de consumo y necesitábamos diseñar las fricciones con modularidad y escalabilidad en su núcleo. Para implementar una solución a más largo plazo, optamos por construir una biblioteca común para todas nuestras fricciones. Esto nos ayuda a integrarnos fácilmente en diferentes flujos de trabajo y aplicaciones a medida que crecemos.
¿Por qué son importantes las fricciones y por qué una biblioteca para las fricciones?
Las fricciones de usuario son similares a los puntos de control en una carretera y se asignan a los usuarios que nuestro motor de riesgo considera arriesgados y previenen eficazmente el fraude en las aplicaciones antes incluso de que se produzca. La mayoría de las fricciones, como la autenticación multifactor(MFA) o 3-D Secure(3DS), recopilan y verifican los datos del usuario para garantizar su autenticidad. La información que recopilamos puede ser conocida o adicional.
Por ejemplo, tenemos una fricción de reintroducción de tarjeta de crédito que requiere que los usuarios vuelvan a introducir la información guardada de su tarjeta en el momento de la compra cuando nuestro motor de riesgos detecta una transacción potencialmente fraudulenta. Si se produce una toma de posesión no autorizada de la cuenta, el defraudador (que no tiene acceso a la tarjeta de crédito original) no podrá proceder a realizar una transacción.
A medida que DoorDash crece rápidamente, se nos ha hecho cada vez más difícil implementar e integrar nuevas fricciones con la suficiente rapidez en respuesta a los ataques de fraude. Decidimos que necesitábamos una biblioteca común para todas nuestras fricciones, de modo que las diferentes aplicaciones pudieran integrarse rápidamente. Este enfoque nos permite
- Introducir la fricción en diferentes flujos de trabajo y aplicaciones
- Aumentar la propiedad de la lógica para hacer que las fricciones
- Evitar el riesgo de errores o disparidades con múltiples implantaciones
- Disponer de un punto común de fallo para depurar y clasificar los problemas
- Evite el código DRY (no se repita)
Creación de una biblioteca de fricciones de riesgo
Creamos nuestra biblioteca web component-risk
con la interoperabilidad y la modularidad en mente, para reducir el tiempo necesario para integrar las fricciones de riesgo en cualquier flujo de trabajo. Algunos ejemplos de nuestras fricciones son:
- Autenticación multifactor mediante SMS/correo electrónico (MFA)
- Verificación de la segunda tarjeta
- Verificación de la reintroducción de la tarjeta
- Número de teléfono/Correo electrónico de verificación
- 3DS
Estas fricciones se utilizan en diferentes flujos de trabajo, como el inicio de sesión, el pago y la edición del perfil, en diferentes aplicaciones de DoorDash y Caviar: consumidor, Dasher (nuestro nombre para el conductor de reparto) y comerciante. Algunos de los flujos de trabajo, como el inicio de sesión, son vistas web integradas que también sirven para flujos de trabajo móviles.
Manténgase informado con las actualizaciones semanales
Suscríbase a nuestro blog de ingeniería para recibir actualizaciones periódicas sobre los proyectos más interesantes en los que trabaja nuestro equipo.
Introduzca una dirección de correo electrónico válida.
Gracias por suscribirse.
Arquitectura de component-risk
Component-risk
es una biblioteca TypeScript que utiliza React.js. Como se muestra en Figura 1, la mayoría de las aplicaciones web React de DoorDash tienen un servicio BFF (backend for frontend) que se utiliza para comunicarse con nuestros microservicios backend. Las aplicaciones consumen la librería component-risk
y la biblioteca puede configurarse para aprovechar el BFF de cada aplicación para las fricciones. Para todas las aplicaciones sin BFF utilizamos el viejo monolito DSJ o nuestro servicio interno llamado risk-bff
.
La pila tecnológica para component-risk
es:
- React.js
- TypeScript
- Componentes estilizados
- Apolo GraphQL
Utilizamos Contextos React para la gestión de estados. Optar por una solución de gestión de estados integrada como React Contexts nos permite simplificar el código y limitar el tamaño del paquete. En su núcleo, component-risk
utiliza múltiples contextos como se describe en Figura 2 para mantener la separación de preocupaciones y gestiona el estado a través de componentes que sirven a diferentes fricciones,
- Contexto de riesgo: Contexto expuesto externamente que gestiona el estado de
component-risk
a nivel de aplicación. Aquí es donde se mantienen estados como cliente apollo, proxy URL. - Contexto de diseño: Contexto interno que gestiona los elementos de diseño de los roces. Esto incluye la configuración del comportamiento CTA para el modal, el control de la naturaleza dismisible del modal, etc.
- Verificar el contexto de la cuenta: Contexto interno para fricciones como MFA y verificación de teléfono/correo electrónico.
- Verificar el contexto de las tarjetas de pago: Contexto interno para algunas de nuestras fricciones de desafío de tarjetas.
- Contexto ThreeDSecure: Contexto interno para mantener las variables de estado de la fricción 3DS.
Obstáculos y consideraciones para la creación de la biblioteca
Aunque la creación de una biblioteca parecía ser la solución para implantar y desplegar rápidamente las fricciones, el diseño y la implantación presentaban algunos problemas. La mayoría de estos retos se referían a la compatibilidad con las personalizaciones de la pila tecnológica por parte de los diferentes equipos y a abordar y aceptar las disparidades que surgen debido a dichas personalizaciones.
Apoyo a un backend fragmentado
Los paquetes web de DoorDash son mantenidos por cada equipo individualmente y el equipo tiene la libertad de diseñarlos como mejor les parezca. Además de que cada equipo adoptó enfoques diferentes, también tuvimos un gran esfuerzo de migración que vio nuestro backend cambiar de un monolito Django a microservicios basados en Kotlin. Todo esto junto causó una disparidad en la forma en que cada aplicación se comunica con el backend y fue uno de los principales obstáculos para nuestra biblioteca. Necesitábamos soportar lo siguiente:
- Aplicaciones que utilizan BFF basado en GraphQL
- Aplicaciones que utilizan servicios basados en REST (incluye nuestro monolito Django - DSJ)
- Aplicaciones que no disponen de BFF y necesitan comunicarse con servicios basados en gRPC
Teníamos que utilizar el mismo código frontend con la flexibilidad necesaria para interactuar con nuestra variada infraestructura backend. Tras varias consideraciones, resolvimos este problema utilizando el enlace REST de Apollo GraphQL. REST link nos permite llamar endpoints dentro de consultas GQL tanto a GraphQL como a un servicio RESTful y tener todos los datos gestionados por Apollo Client. Esto se hace configurando Apollo para soportar múltiples enlaces como:
new ApolloClient({
link: ApolloLink.from([
onError(({ networkError }) => {
if (networkError) {
console.log(`[Network error]: ${networkError}`)
}
}),
new RestLink({
headers,
uri: getBaseUrl(proxyOverride),
credentials: 'include',
fieldNameNormalizer: (key: string) => camelCase(key),
fieldNameDenormalizer: (key: string) => snakeCase(key),
}),
new HttpLink({
headers,
uri: getBffUrl(riskBffConfig),
credentials: 'include',
}),
]),
Y luego, al construir el GQL, especificamos si tiene que ser el del servicio basado en GraphQL o el del servicio RESTful. Esto se hace utilizando la directiva @rest.
1/ Mutation used by HttpLink:
gql`
mutation verifyPasscodeBFF($code: String!, $action: String!) {
verifyPasscode(code: $code, action: $action) {
id
}
}
`
2/ Mutation used by RestLink:
gql`
mutation verifyPasscodeRest($code: String!, $action: String!) {
verifyPasscode(input: { code: $code, action: $action })
@rest(method: "POST", path: "/v1/mfa/verify", type: "VerifyPasscode") {
id
}
}
`
Aterrizaje en un paradigma de diseño común
Al ser una biblioteca de múltiples fricciones, teníamos casos de uso para soportar fricciones intercambiables en un único flujo de trabajo. Por ejemplo, tenemos MFA, reintroducción de tarjeta, segundo reto de tarjeta y 3DS, todo ello en la caja, y cualquier usuario podría obtener una o una combinación de estas en función de sus evaluaciones de riesgo.
Para conseguirlo, tuvimos que decidirnos por un paradigma de diseño común para todas nuestras fricciones, que nos permitiera reutilizar gran parte del código repetitivo y simplemente cambiar el contexto de cada una. Optamos por utilizar un modal para todas las fricciones. Esto nos permite:
- Conservar el estado del flujo de trabajo, ya que la fricción es sólo una superposición dura.
- No apartar al usuario de lo que está haciendo
- Mantenga la fricción lo suficientemente rápida para que la experiencia del usuario no se vea obstaculizada en exceso.
Adaptación a diferentes temas
Tenemos diferentes requisitos de tematización para distintas aplicaciones, como consumidores, comerciantes, etc. Queríamos que nuestras fricciones adoptaran la tematización, como puede verse en la figura 4, en función de la aplicación que la esté renderizando. Esto lo conseguimos haciendo que todas las dependencias DLS (Design Language System) fueran dependencias entre pares, de modo que pudiéramos utilizar el mismo contexto temático en todas las aplicaciones y la biblioteca.
Minimizar el tamaño del paquete
Uno de los retos técnicos de nuestra librería era asegurarnos de mantener el tamaño de nuestros bundles lo más pequeño posible. Dado que las bibliotecas no tienen webpack bundling code splitting y tree-shaking no era realmente una opción, optamos por utilizar tanto JavaScript nativo como fuera posible y utilizar bibliotecas externas que se utilizan comúnmente en las aplicaciones de DoorDash. Por ejemplo, utilizamos el mismo cargador analytics.js y creamos una instancia i18n separada de la aplicación. Esto se consiguió definiendo las dependencias respectivas como una dependencia de pares.
Posibilitar el desarrollo local
El objetivo de component-risk
es permitir una integración perfecta en nuestras aplicaciones y mejorar la velocidad de los desarrolladores. Al desarrollar localmente, es importante que component-risk
seguir el mismo entorno que el de la aplicación. Sería difícil desarrollar con la aplicación en producción y component-risk
confiando en el staging. Superamos esto aprovechando los proxies webpack de la aplicación y permitiendo que la aplicación configure la URL de la biblioteca basándose en los proxies.
El futuro: Más allá de la web
Como parte del equipo antifraude, queríamos desarrollar soluciones que trascendieran nuestra infraestructura actual y asegurarnos de que la mitigación de riesgos se incluyera en cada ciclo de vida del desarrollo de software. Disponer de una biblioteca común como component-risk
nos ayuda a conseguirlo, ya que la facilidad de integración impide que la mitigación de riesgos sea una ocurrencia tardía.
Hemos protegido algunos de los flujos de trabajo más importantes de DoorDash, como el inicio de sesión y el pago, utilizando nuestra biblioteca. Hasta ahora tenemos casi 15 flujos de trabajo en aplicaciones para consumidores, logística y comercios que utilizan... component-risk
en producción durante casi dos años sin averías importantes.
Tras la creación de nuestra biblioteca, hemos visto cómo nuestro tiempo de desarrollo se reducía de meses a semanas para la mayoría de las iniciativas importantes. Por ejemplo, hemos sido capaces de proteger nuestros flujos de trabajo de identidad, como el inicio de sesión, el registro y el olvido de contraseña, con la fricción MFA en unos pocos meses de esfuerzo. Sin embargo, aún tenemos margen de mejora para nuestra biblioteca y ambicionamos ampliar sus casos de uso. He aquí una lista de elementos compilados para la Estrella del Norte de nuestra biblioteca:
- Consolidar todo nuestro código backend en un único BFF, que nos permitirá poseer y desarrollar
component-risk
como solución integral. - Intégrese con nuestro servicio de identidad para autenticar las llamadas del backend sin tener como intermediario a las BFF de la aplicación correspondiente.
- Dividir cada categoría de fricciones en su propia biblioteca secundaria para mejorar el tamaño del paquete y también dejar que nuestros equipos secundarios posean la biblioteca secundaria respectiva.
- La mayoría de las fricciones son extremadamente sencillas en su experiencia de usuario y no requieren necesariamente soluciones nativas para plataformas móviles. Vemos una gran oportunidad en la creación de fricciones híbridas que también puedan utilizarse como webviews para plataformas iOS y Android.
Si le interesa crear soluciones como éstas y luchar contra el fraude con nosotros, considere la posibilidad de unirse a nuestro equipo.