Pour protéger les comptes de nos utilisateurs et prévenir la fraude, nous demandons parfois aux utilisateurs de vérifier leur identité ou de confirmer une transaction en effectuant une "friction utilisateur" telle que l'authentification à deux facteurs. Les frictions utilisateur, ou les étapes de vérification conçues pour prévenir la fraude, sont essentielles pour lutter contre les activités frauduleuses, mais ne sont pas toujours faciles à mettre en œuvre rapidement. Chez DoorDash, nous constatons parfois que notre suite d'applications fait l'objet d'abus et d'attaques en raison de failles imprévues. Dans de tels scénarios, l'introduction d'une fonction de friction dans ces flux de travail est efficace pour prévenir d'autres dommages.
Notre implémentation initiale des frictions s'est concentrée uniquement sur l'application grand public DoorDash. Bien que cela ait été efficace à court terme, nous avons rapidement réalisé que les cas d'utilisation s'étendaient au-delà de notre application grand public et que nous devions concevoir les frictions avec la modularité et l'évolutivité au cœur de leurs préoccupations. Pour mettre en œuvre une solution à plus long terme, nous avons opté pour la création d'une bibliothèque commune à toutes nos frictions. Cela nous permet de nous intégrer facilement à différents flux de travail et applications au fur et à mesure de notre croissance.
Pourquoi les frictions sont-elles importantes et pourquoi une bibliothèque pour les frictions ?
Les frictions utilisateur sont similaires à des points de contrôle sur une route et sont assignées aux utilisateurs que notre moteur de risque considère comme risqués et empêchent efficacement la fraude dans les applications avant même qu'elle ne se produise. La plupart des restrictions, telles que l'authentification multifactorielle(MFA) ou 3-D Secure(3DS), collectent et vérifient les données de l'utilisateur pour garantir l'authenticité. Les informations que nous recueillons peuvent être connues ou supplémentaires.
Par exemple, nous disposons d'une friction de ressaisie de la carte de crédit qui oblige les utilisateurs à ressaisir les informations enregistrées sur leur carte au moment du paiement lorsque notre moteur de risque détecte une transaction potentiellement frauduleuse. En cas de prise de contrôle non autorisée d'un compte, le fraudeur (qui n'a pas accès à la carte de crédit d'origine) ne pourra pas effectuer de transaction.
Avec la croissance rapide de DoorDash, il est devenu de plus en plus difficile pour nous d'implémenter et d'intégrer de nouvelles frictions assez rapidement en réponse aux attaques de fraude. Nous avons déterminé que nous avions besoin d'une bibliothèque commune pour toutes nos frictions afin que différentes applications puissent être intégrées rapidement. Cette approche nous permet de :
- Introduire les frictions dans les différents flux de travail et applications
- Augmenter l'appropriation de la logique pour éliminer les frictions
- Éviter le risque de bogues ou de disparités en cas d'implémentations multiples
- Disposer d'un point commun de défaillance pour le débogage et le triage des problèmes
- Prévenir le code DRY (ne pas se répéter)
Création d'une bibliothèque pour les frictions de risque
Nous avons créé notre bibliothèque web component-risk
avec l'interopérabilité et la modularité à l'esprit, afin de réduire le temps nécessaire à l'intégration des frictions de risque dans n'importe quel flux de travail. Voici quelques exemples de nos frictions :
- Authentification multifactorielle par SMS/Email (MFA)
- Deuxième vérification de la carte
- Vérification de la réinscription de la carte
- Numéro de téléphone/vérification de l'adresse électronique
- 3DS
These frictions are used across different workflows, including login, checkout, and edit profile, in different DoorDash and Caviar applications--consumer, Dasher (our name for delivery driver), and merchant. Some of the workflows, such as login, are integrated web views by then also serving mobile workflows.
Restez informé grâce aux mises à jour hebdomadaires
Abonnez-vous à notre blog d'ingénierie pour recevoir régulièrement des informations sur les projets les plus intéressants sur lesquels notre équipe travaille.
Veuillez saisir une adresse électronique valide.
Merci de vous être abonné !
Architecture des component-risk
Component-risk
est une bibliothèque TypeScript utilisant React.js. Comme le montre l'article Figure 1, La plupart des applications web React de DoorDash ont un service BFF (backend for frontend) qui est utilisé pour communiquer avec nos microservices backend. Les applications consomment la bibliothèque component-risk
et la bibliothèque peut être configurée pour exploiter le BFF de chaque application pour les frictions. Pour toutes les applications sans BFF, nous utilisons le vieux monolithe DSJ ou notre service interne appelé risk-bff
.
La pile technologique pour component-risk
est :
- React.js
- TypeScript
- Composants stylisés
- Apollo GraphQL
Nous utilisons Contextes React pour la gestion des états. Opter pour une solution de gestion d'état intégrée comme React Contexts nous permet de garder le code simple et de limiter la taille du bundle. Au cœur de la solution, component-risk
utilise des contextes multiples tels que décrits dans Figure 2 pour maintenir la séparation des préoccupations et gérer l'état à travers les composants qui servent des frictions différentes,
- Contexte de risque : Contexte exposé à l'extérieur qui gère l'état des
component-risk
au niveau de l'application. C'est à ce niveau que des états tels que le client Apollo ou le proxy URL sont maintenus. - Contexte de mise en page : Contexte interne qui gère les éléments de conception des frictions. Il s'agit notamment de définir le comportement du CTA pour la fenêtre modale, de contrôler la nature inadmissible de la fenêtre modale, etc.
- Vérifier le contexte du compte : Contexte interne pour les frictions telles que l'AMF et la vérification par téléphone ou par courrier électronique.
- Vérifier le contexte des cartes de paiement : Contexte interne pour certaines de nos frictions concernant les cartes de paiement.
- Contexte ThreeDSecure : Contexte interne pour le maintien des variables d'état pour la friction 3DS.
Obstacles et considérations liés à la création de la bibliothèque
Même si la création d'une bibliothèque semblait être la solution pour mettre en œuvre et déployer rapidement les frictions, elle comportait son lot de défis au niveau de la conception et de la mise en œuvre. La plupart de ces défis concernaient la prise en charge des personnalisations de la pile technologique par les différentes équipes et la prise en compte des disparités résultant de ces personnalisations.
Prise en charge d'un backend fragmenté
Les paquets web de DoorDash sont maintenus par chaque équipe individuellement et l'équipe a la liberté de les concevoir comme elle l'entend. En plus de l'adoption par chaque équipe d'approches différentes, nous avons également eu un énorme effort de migration qui a vu notre backend passer d'un monolithe Django à des microservices basés sur Kotlin. Tout cela a provoqué une disparité dans la façon dont chaque application communique avec le backend et a été l'un des principaux obstacles pour notre bibliothèque. Nous devions prendre en charge les éléments suivants :
- Applications utilisant BFF basé sur GraphQL
- Applications that use REST-based services (includes our Doorstep Django - DSJ monolith)
- Les applications qui n'ont pas de BFF et qui doivent communiquer avec des services basés sur gRPC
Nous devions utiliser le même code frontal avec la flexibilité d'interagir avec notre infrastructure backend variée. Après plusieurs réflexions, nous avons résolu ce problème en utilisant le lien REST d'Apollo GraphQL. Le lien REST nous permet d'appeler des points d'extrémité à l'intérieur de requêtes GQL à la fois vers GraphQL et un service RESTful et d'avoir toutes les données gérées par Apollo Client. Pour ce faire, il suffit de configurer Apollo pour qu'il prenne en charge plusieurs liens tels que :
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',
}),
]),
Ensuite, lors de la construction du GQL, nous spécifions s'il doit être celui pour le service basé sur GraphQL ou celui pour le service RESTful. Cela se fait à l'aide de la directive @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
}
}
`
Atterrir sur un paradigme de conception commun
Étant une bibliothèque à frictions multiples, nous avions des cas d'utilisation pour la prise en charge de frictions interchangeables dans un flux de travail unique. Par exemple, nous avons l'AMF, la réintégration de la carte, la deuxième vérification de la carte, et le 3DS à la caisse, et chaque utilisateur peut recevoir une ou plusieurs de ces mesures en fonction de son évaluation des risques.
Pour y parvenir, nous avons dû décider d'un paradigme de conception commun pour toutes nos frictions, ce qui nous permet de réutiliser une grande partie du code de base et de changer simplement le contexte de chacune d'entre elles. Nous avons choisi d'utiliser une fenêtre modale pour toutes les frictions. Cela nous permet de :
- Conserver l'état du flux de travail puisque la friction n'est qu'un recouvrement dur.
- Ne pas détourner l'utilisateur de son travail
- La friction doit être suffisamment rapide pour que l'expérience de l'utilisateur ne soit pas trop entravée.
S'adapter aux différents thèmes
Nous avons des exigences différentes en matière de thématisation pour différentes applications telles que le consommateur, le commerçant, etc. Nous voulions que nos frictions adoptent la thématisation, comme le montre la figure 4, en fonction de l'application qui effectue le rendu. Pour ce faire, nous avons fait en sorte que toutes les dépendances DLS (Design Language System) soient des dépendances entre pairs, afin de pouvoir utiliser le même contexte de thématisation pour toutes les applications et la bibliothèque.
Minimiser la taille des paquets
L'un des défis techniques de notre bibliothèque était de s'assurer que la taille de nos bundles était aussi petite que possible. Puisque les bibliothèques n'ont pas de division de code webpack bundling et que le tree-shaking n'était pas vraiment une option, nous avons opté pour l'utilisation d'autant de JavaScript natif que possible et pour l'utilisation de bibliothèques externes qui sont couramment utilisées dans les applications DoorDash. Par exemple, nous avons utilisé le même chargeur analytics.js et créé une instance i18n séparée de l'application. Ceci a été réalisé en définissant les dépendances respectives comme une dépendance de pair.
Permettre le développement local
L'objectif de la component-risk
est de permettre une intégration transparente dans nos applications et d'améliorer la vélocité des développeurs. Lors du développement local, il est important que component-risk
de suivre le même environnement que celui de l'application. Il serait difficile de développer alors que l'application est en production et que l'environnement de l'application n'est pas le même que celui de l'application. component-risk
en s'appuyant sur staging. Nous avons résolu ce problème en exploitant les proxies webpack de l'application et en permettant à l'application de configurer l'URL de la bibliothèque en fonction des proxies.
L'avenir : Au-delà du web
En tant que membre de l'équipe chargée de la lutte contre la fraude, nous voulions mettre au point des solutions qui transcendent notre infrastructure actuelle et veiller à ce que l'atténuation des risques soit intégrée dans chaque cycle de vie du développement logiciel. Disposer d'une bibliothèque commune telle que component-risk
nous aide à atteindre cet objectif, car la facilité d'intégration permet d'éviter que l'atténuation des risques ne soit qu'une réflexion après coup.
Certains des flux de travail les plus critiques de DoorDash, tels que la connexion, la caisse, ont été protégés par notre bibliothèque. Jusqu'à présent, nous avons près de 15 flux de travail à travers les applications des consommateurs, de la logistique et des marchands qui utilisent component-risk
en production depuis près de deux ans sans panne majeure.
Après la création de notre bibliothèque, nous avons vu notre temps de développement passer de plusieurs mois à quelques semaines pour la plupart des initiatives majeures. Par exemple, nous avons pu protéger nos flux de travail d'identité tels que la connexion, l'inscription et l'oubli de mot de passe avec une friction MFA en quelques mois d'efforts. Cependant, nous avons encore des possibilités d'amélioration pour notre bibliothèque et nous avons l'ambition d'étendre ses cas d'utilisation. Voici une liste d'éléments compilés pour l'étoile polaire de notre bibliothèque :
- Consolider tout notre code backend dans un seul BFF, ce qui nous permettra de posséder et de développer
component-risk
comme une solution de bout en bout. - Intégrer notre service d'identité pour authentifier les appels au backend sans que les BFF de l'application concernée ne servent d'intermédiaire.
- Répartir chaque catégorie de frictions dans sa propre sous-bibliothèque afin d'améliorer la taille du paquet et de permettre à nos sous-équipes de posséder la sous-bibliothèque correspondante.
- Most frictions are extremely simple in their user experience and don't necessarily require native solutions for mobile platforms. We see a huge opportunity in creating hybrid frictions that can also be used as webviews for iOS and Android platforms.
Si vous souhaitez élaborer des solutions de ce type et lutter avec nous contre la fraude, n'hésitez pas à rejoindre notre équipe !