Security & Privacy
Créer une application de chat Android chiffrée RGPD
53 min
comment créer une application de chat conforme au rgpd introduction ahoy communauté back4app! ceci est un tutoriel invité de l'équipe de virgil security, inc https //virgilsecurity com/?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat nous sommes la technologie cryptographique derrière la messagerie chiffrée de bout en bout de twilio https //www twilio com/blog/2016/05/introducing end to end encryption for twilio ip messaging with virgil security html nos amis @ back4app nous ont demandé de vous montrer comment construire une application de chat chiffrée de bout en bout sur back4app dans cet article, nous vous guiderons à travers les étapes pour rendre une simple application messenger android back4app chiffrée de bout en bout ! êtes vous prêt ? ps si les détails ne vous intéressent pas, passez simplement à la fin de l'article et téléchargez le produit final qu'est ce que le chiffrement de bout en bout ? tout d'abord, commençons par un rapide rappel de ce qu'est le e2ee (chiffrement de bout en bout) et comment cela fonctionne le e2ee est simple lorsque vous tapez un message dans un chat, il est chiffré sur votre appareil mobile (ou dans votre navigateur) et n'est déchiffré que lorsque votre partenaire de chat le reçoit et souhaite l'afficher dans la fenêtre de chat le message reste chiffré pendant qu'il voyage sur wi fi et internet, à travers le cloud / serveur web, dans une base de données, et sur le chemin du retour vers votre partenaire de chat en d'autres termes, aucun des réseaux ou serveurs n'a la moindre idée de ce dont vous parlez tous les deux ce qui est difficile dans le chiffrement de bout en bout, c'est la tâche de gérer les clés de chiffrement de manière à ce que seuls les utilisateurs impliqués dans le chat puissent y accéder et personne d'autre et quand je dis "personne d'autre", je le pense vraiment même les personnes en interne de votre fournisseur de cloud ou même vous, le développeur, êtes exclus ; aucune erreur accidentelle https //techcrunch com/2017/11/29/meet the man who deactivated trumps twitter account/ ou un regard légalement imposé ne sont possibles écrire du code cryptographique, surtout pour plusieurs plateformes, est difficile générer de vrais nombres aléatoires, choisir les bons algorithmes et choisir les bons modes de chiffrement ne sont que quelques exemples qui font que la plupart des développeurs lèvent les mains en l'air et finissent par ne pas le faire cet article de blog vous montrera comment ignorer tous ces détails ennuyeux et chiffrer rapidement et simplement de bout en bout en utilisant le sdk de virgil pour une introduction, voici comment nous allons mettre à niveau l'application de messagerie de back4app pour qu'elle soit chiffrée de bout en bout lors de l'inscription nous générerons les clés privées et publiques individuelles pour les nouveaux utilisateurs (rappelez vous la clé publique du destinataire chiffre les messages et la clé privée correspondante du destinataire les déchiffre) avant d'envoyer des messages, vous chiffrerez les messages de chat avec la clé publique du destinataire après avoir reçu des messages, vous déchiffrerez les messages de chat avec la clé privée du destinataire nous publierons les clés publiques des utilisateurs sur le service de cartes de virgil afin que les utilisateurs de chat puissent se rechercher et chiffrer des messages les uns pour les autres les clés privées resteront sur les appareils des utilisateurs gardez le simple ceci est la mise en œuvre la plus simple possible d'un chat e2ee et cela fonctionne parfaitement pour des applications de chat simples entre 2 utilisateurs où les conversations sont de courte durée et il est acceptable de perdre l'historique des messages si un appareil est perdu avec la clé privée dessus d'accord, assez parlé ! passons au codage nous commencerons par vous guider à travers la configuration de l'application android, ensuite, nous ajouterons du code pour rendre l'application chiffrée de bout en bout prérequis pour compléter ce tutoriel, vous avez besoin de android studio une application créée sur back4app suivez le tutoriel créer une nouvelle application pour apprendre à créer une application sur back4app inscrivez vous pour un compte virgil security (nous créerons l'application plus tard) configurons l'application de messagerie "propre" back4app 1 configurez votre serveur d'application commençons par déployer la fonction cloud pour cela, vous aurez besoin de trouvez main js main js et package json package json dans le répertoire scripts scripts ; ouvrez main js main js avec votre éditeur préféré 1 1) obtenez les identifiants back4app ouvrez dashboard dashboard de votre application > paramètres de l'application paramètres de l'application > sécurité et clés sécurité et clés dans main js main js , remplacez parse app id parse app id par votre id d'application id d'application et parse rest api key parse rest api key par votre clé api rest clé api rest 1 2) obtenez les identifiants virgil créez une application sur tableau de bord virgil https //dashboard virgilsecurity com/ ouvrez votre nouvelle application virgil, naviguez vers la section e3kit et générez un env env fichier sous la section e3kit dans la barre latérale gauche copiez les valeurs de app id app id , app key app key , et app key id app key id du fichier env env remplacez les valeurs copiées dans votre main js main js fichier dans les champs correspondants ( main js main js de scripts scripts répertoire) 1 3) déployez la fonction de code cloud ouvrez le “tableau de bord” de votre application back4app > “core” > fonctions de code cloud; cliquez sur +ajouter et sélectionnez votre main js et package json (du répertoire scripts), après cela déplacez les tous les deux dans le dossier cloud; cliquez sur déployer 2 démarrer l'application de démonstration kotlin back4app n'oubliez pas de configurer d'abord la fonction de code cloud back4app c'est une partie obligatoire de cette démonstration après cela, suivez les étapes suivantes 2 1) importer le projet dans android studio ouvrez android studio > fichier fichier > nouveau nouveau > projet à partir du contrôle de version projet à partir du contrôle de version > git git url du dépôt git https //github com/virgilsecurity/chat back4app android vérifiez la clean chat kt clean chat kt branche important ! sélectionnez le type de fichier « projet » de l'arborescence il sera utilisé tout au long du tutoriel 2 2) configurer les identifiants back4app dans le projet ouvrez le « tableau de bord » de votre application back4app > « paramètres de l'application » > « sécurité et clés »; allez au /app/src/main/res/values/strings xml /app/src/main/res/values/strings xml fichier dans votre projet android et remplacez your back4app app id your back4app app id par votre id d'application id d'application et your back4app client key your back4app client key par votre clé client clé client 2 3) configurer la db ouvrez back4app “tableau de bord” > “noyau” > “navigateur de base de données” > “créer une classe” et créez des classes de personnalisé personnalisé de type nommé message message et chatthread chatthread ; 2 4) configurer la requête en direct retournez à votre compte back4app https //dashboard back4app com/apps/#!/admin appuyez sur le paramètres du serveur paramètres du serveur bouton sur votre application trouvez le bloc “hébergement web et requête en direct” ouvrez les paramètres de la requête en direct et vérifiez l'option activer l'hébergement activer l'hébergement choisissez un nom pour votre sous domaine pour activer la requête en direct pour les 2 classes que vous avez créées message message et chatthread chatthread copiez le nom de votre nouveau sous domaine et cliquez sur le bouton enregistrer retournez à /app/src/main/res/values/strings xml /app/src/main/res/values/strings xml et collez le “nom de sous domaine” que vous avez saisi ci dessus dans le back4app live query url back4app live query url au lieu de “yoursubdomainname” après ces étapes, vous pourrez appuyer sur le bouton exécuter dans android studio et faire fonctionner l'exemple utilisez un émulateur ou un appareil réel pour le tester 3 exécutez la démo propre pour voir le résultat de l'exécution de la version propre de la démo, vous devrez inscrire 2 utilisateurs ; démarrer une conversation entre eux et envoyer quelques messages ; 3\ ouvrez back4app “tableau de bord” > “noyau” > “navigateur de base de données” > “message” si tout a bien fonctionné, vous devriez voir l'application de messagerie apparaître enregistrez deux utilisateurs et envoyez quelques messages l'un à l'autre vous devriez voir de nouvelles données apparaître dans le message message classe notez que vous pouvez voir sur le serveur de quoi vos utilisateurs discutent suivant fermez votre interface de chat et passez à l'étape suivante – ajout de chiffrement e2ee maintenant, chiffrons ces messages de bout en bout ! à la fin de cette partie, voici à quoi ressembleront vos messages de chat sur le serveur pouvez vous repérer la différence ? comment y parvenir ? évidemment, nous devons mettre en œuvre le chiffrement de bout en bout, ce qui signifie que notre application doit générer la paire de clés privée et publique dans le cadre de l'inscription stocker la clé privée dans le stockage de clés sur l'appareil de l'utilisateur publier la clé publique dans le service de cartes de virgil en tant que "carte virgil" pour que d'autres utilisateurs puissent la télécharger et chiffrer des messages avec chiffrer les messages avec la clé publique et signer avec la clé privée ; déchiffrer les messages avec la clé privée et vérifier avec la clé publique pour cela, nous devrons ajouter e3kit à notre application de démonstration propre et ajouter un peu plus de code pour mettre en œuvre tout ce qui a été décrit ci dessus mais avant de commencer, clarifions deux termes importants pour vous qu'est ce qu'une carte virgil et une clé privée ? carte virgil les cartes virgil portent les clés privées des utilisateurs les cartes virgil sont publiées dans le service de cartes de virgil (imaginez que ce service est comme un annuaire téléphonique) pour que d'autres utilisateurs puissent les récupérer alice doit récupérer la clé publique de bob pour chiffrer un message pour bob en utilisant cette clé clé privée une partie privée de la clé de chiffrement rappelez vous, les clés privées peuvent déchiffrer des données qui ont été chiffrées en utilisant la clé publique correspondante 1 ajouter e3kit à la démonstration propre e3kit back4app kotlin dans le niveau de l'application ( module app module app ) gradle à /app/build gradle /app/build gradle ajoutez (mais ne synchronisez pas encore les scripts gradle) ajoutez ce qui suit à la fin de votre niveau de projet /build gradle /build gradle ( projet chat back4app android projet chat back4app android ) maintenant, vous pouvez synchroniser les scripts gradle ; mettez à jour votre appvirgil appvirgil classe en ajoutant de nouveaux champs appuyez sur alt+ enter alt+ enter pour importer les bibliothèques nécessaires dans la classe 2 authentifier les utilisateurs avec back4app cloud code dans le /virgilsecurity/virgilback4app/model/ /virgilsecurity/virgilback4app/model/ répertoire, créez des classes de données authenticateresponse authenticateresponse et virgiljwtresponse virgiljwtresponse qui représentent les réponses des fonctions cloud code dans le /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ créez un authrx authrx objet qui implémente les appels aux fonctions cloud code (n'oubliez pas d'importer toutes les bibliothèques nécessaires par la suite) 1 object authrx { 2 3 / 4 you can call it only after successful \[authenticate] 5 / 6 fun virgiljwt(sessiontoken string) = single create\<string> { emitter > 7 val requestparams = mutablemapof\<string, string>() apply { 8 put("sessiontoken", sessiontoken) 9 } 10 11 parsecloud callfunctioninbackground\<map\<string, any>>( 12 key virgil jwt, 13 requestparams 14 ) { virgiljwt, exception > 15 if (exception == null) 16 emitter onsuccess(virgiljwt\[key token] tostring()) 17 else 18 emitter onerror(exception) 19 20 } 21 } 22 23 private const val key virgil jwt = "virgil jwt" 24 private const val key token = "token" 25 } 3 stocker le jwt virgil localement le jeton virgil reçu des fonctions cloud code doit être stocké localement mettons à jour préférences préférences la classe dans /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ définir la constante ajouter les fonctions setvirgiltoken setvirgiltoken , virgiltoken virgiltoken et clearvirgiltoken clearvirgiltoken kotlin fun setvirgiltoken(virgiltoken string) { with(sharedpreferences edit()) { putstring(key virgil token, virgiltoken) apply() } } fun virgiltoken() string? { with(sharedpreferences) { return getstring(key virgil token, null) } } fun clearvirgiltoken() { with(sharedpreferences edit()) { remove(key virgil token) apply() } } 1 virgil token should be reset on logout let's add `preferences instance(this) clearvirgiltoken()` line into `initdrawer` function of `threadslistactivity` class (in ` /virgilsecurity/virgilback4app/chat/contactslist/`) 2 kotlin 3 private fun initdrawer() { 4 5 nvnavigation setnavigationitemselectedlistener { item > 6 r id itemlogout > { 7 dldrawer closedrawer(gravitycompat start) 8 presenter disposeall() 9 showbaseloading(true) 10 // new code >> 11 preferences instance(this) clearvirgiltoken() 12 // << new code 13 14 } 15 } 16 } 4 modifier l'enregistrement de l'utilisateur e3kit s'occupe de vos clés privées et publiques pour les générer lors du processus d'enregistrement, nous devrons faire ce qui suit dans /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ créer rxethree rxethree classe maintenant, ajoutez initethree initethree fonction qui initialise l'instance e3kit dans rxethree rxethree classe 1 import com virgilsecurity android common model ethreeparams 2 import com virgilsecurity android ethree interaction ethree 3 4 5 fun initethree(identity string, verifyprivatekey boolean = false) 6single\<ethree> = single create { e > 6 val params = ethreeparams(identity, {preferences virgiltoken()!!}, context) 7 val ethree = ethree(params) 8 if (verifyprivatekey) { 9 if (ethree haslocalprivatekey()) { 10 e onsuccess(ethree) 11 } else { 12 e onerror(keyentrynotfoundexception()) 13 } 14 } else { 15 e onsuccess(ethree) 16 } 17 } ajoutez registerethree registerethree fonction qui enregistre un nouvel utilisateur dans rxethree rxethree classe e3kit génère une paire de clés lors de l'inscription la clé privée générée est ensuite stockée dans le stockage local, et la clé publique est publiée auprès des services virgil en tant que carte virgil 1 import com android virgilsecurity virgilback4app appvirgil 2 import com virgilsecurity common callback oncompletelistener 3 import io reactivex completable 4 5 6 fun registerethree() completable = completable create { e > 7 appvirgil ethree register() addcallback(object oncompletelistener { 8 override fun onerror(throwable throwable) { 9 e onerror(throwable) 10 } 11 12 override fun onsuccess() { 13 e oncomplete() 14 } 15 16 }) 17 } faisons quelques mises à jour sur loginpresenter loginpresenter classe (dans /virgilsecurity/virgilback4app/auth/) /virgilsecurity/virgilback4app/auth/) ajouter rxethree rxethree champ 1 private val rxethree = rxethree(context) mettez à jour le requestsignup requestsignup fonction pour exécuter l'enregistrement avec e3kit 1 fun requestsignup(identity string, onsuccess () > unit, onerror (throwable) > unit) { 2 val password = generatepassword(identity tobytearray()) 3 4 val disposable = rxparse signup(identity, password) 5 subscribeon(schedulers io()) 6 observeon(schedulers io()) 7 // new code >> 8 tosingle { parseuser getcurrentuser() } 9 flatmap { authrx virgiljwt(it sessiontoken) } 10 map { preferences setvirgiltoken(it) } 11 flatmap { rxethree initethree(identity) } 12 map { appvirgil ethree = it } 13 flatmap { rxethree registerethree() tosingle { unit } } 14 // << new code 15 observeon(androidschedulers mainthread()) 16 // updated code >> 17 subscribeby( 18 onsuccess = { 19 onsuccess() 20 }, 21 onerror = { 22 onerror(it) 23 } 24 ) 25 // << updated code 26 27 compositedisposable += disposable 28 } 5 modifier les fonctions de connexion maintenant, apportons des modifications à requestsignin requestsignin méthode de loginpresenter loginpresenter classe (dans /virgilsecurity/virgilback4app/auth/) /virgilsecurity/virgilback4app/auth/) 1 fun requestsignin(identity string, 2 onsuccess () > unit, 3 onerror (throwable) > unit) { 4 5 val password = generatepassword(identity tobytearray()) 6 7 val disposable = rxparse login(identity, password) 8 subscribeon(schedulers io()) 9 observeon(schedulers io()) 10 // new code >> 11 flatmap { authrx virgiljwt(it sessiontoken) } 12 map { preferences setvirgiltoken(it) } 13 flatmap { rxethree initethree(identity, true) } 14 map { appvirgil ethree = it } 15 // << new code 16 observeon(androidschedulers mainthread()) 17 // updated code >> 18 subscribeby( 19 onsuccess = { 20 onsuccess() 21 }, 22 onerror = { 23 onerror(it) 24 } 25 ) 26 // << updated code 27 28 compositedisposable += disposable 29 } 6 obtenez la liste des chats existants ensuite, ajoutez des fonctions qui gèrent l'initialisation d'e3kit dans threadslistfragment threadslistfragment classe (dans /virgilsecurity/virgilback4app/chat/contactslist/) /virgilsecurity/virgilback4app/chat/contactslist/) 1 private fun oninitethreesuccess() { 2 presenter requestthreads(parseuser getcurrentuser(), 3 20, 4 page, 5 const tablenames created at criteria, 6 ongetthreadssuccess, 7 ongetthreadserror) 8 } 9 10 private fun oninitethreeerror(throwable throwable) { 11 showprogress(false) 12 if (adapter itemcount == 0) 13 tverror visibility = view\ visible 14 15 utils toast(activity, utils resolveerror(throwable)) 16 } mettre à jour postcreateinit postcreateinit fonction pour initialiser e3kit 1 override fun postcreateinit() { 2 3 presenter = threadslistfragmentpresenter(activity) 4 5 showprogress(true) 6 // updated code >> 7 if (appvirgil isethreeinitialized()) { 8 presenter requestthreads(parseuser getcurrentuser(), 9 20, 10 page, 11 const tablenames created at criteria, 12 ongetthreadssuccess, 13 ongetthreadserror) 14 } else { 15 presenter requestethreeinit(parseuser getcurrentuser(), oninitethreesuccess, oninitethreeerror) 16 } 17 // << updated code 18 } et ajoutez le code suivant dans threadslistfragmentpresenter threadslistfragmentpresenter classe dans virgilsecurity virgilback4app chat contactslist/ virgilsecurity virgilback4app chat contactslist/ ajouter un champ 1 private val rxethree = rxethree(context) et fonction 1 fun requestethreeinit(currentuser parseuser, onsuccess () > unit, onerror (throwable) > unit) { 2 val disposable = rxethree initethree(currentuser username) 3 subscribeon(schedulers io()) 4 observeon(androidschedulers mainthread()) 5 subscribeby( 6 onsuccess = { 7 appvirgil ethree = it 8 onsuccess() 9 }, 10 onerror = { 11 onerror(it) 12 } 13 ) 14 15 compositedisposable += disposable 16 } à ce stade, nous sommes capables d'inscrire/identifier un utilisateur et de créer un nouveau chat avec un autre utilisateur maintenant, ajoutons le chiffrement pour nos messages 7 chiffrement et déchiffrement des messages ajoutons la findcard findcard fonction à rxethree rxethree classe (dans /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ ) qui nous aidera à obtenir la dernière carte virgil par nom d'utilisateur 1 fun findcard(identity string) single\<card> = single create { e > 2 appvirgil ethree finduser(identity) addcallback(object onresultlistener\<card> { 3 override fun onerror(throwable throwable) { 4 e onerror(throwable) 5 } 6 7 override fun onsuccess(result card) { 8 e onsuccess(result) 9 } 10 11 }) 12 } lorsque un chat est ouvert, nous devrions obtenir la carte virgil du destinataire éditez postcreateinit postcreateinit de chatthreadfragment chatthreadfragment classe (dans /virgilsecurity/virgilback4app/chat/thread/ /virgilsecurity/virgilback4app/chat/thread/ ) en remplaçant 1 presenter requestmessages(thread, 2 50, 3 page, 4 const tablenames created at criteria, 5 ongetmessagessuccess, 6 ongetmessageserror) code avec un nouveau 1 presenter requestcard(recipientid, 2 ongetcardsuccess, 3 ongetcarderror) et ajoutez deux fonctions 1 private fun ongetcardsuccess(card card) { 2 showprogress(false) 3 adapter interlocutorcard = card 4 presenter requestmessages(thread, 5 50, 6 page, 7 const tablenames created at criteria, 8 ongetmessagessuccess, 9 ongetmessageserror) 10 } 11 12 private fun ongetcarderror(t throwable) { 13 if (t is virgilcardisnotfoundexception || t is virgilcardserviceexception) { 14 utils toast(this, 15 "virgil card is not found \nyou can not chat with user without virgil card") 16 activity onbackpressed() 17 } 18 showprogress(false) 19 srlrefresh isrefreshing = false 20 locksendui(lock = false, lockinput = false) 21 22 utils toast(this, utils resolveerror(t)) 23 } maintenant, changeons chatthreadpresenter chatthreadpresenter ajouter des champs 1 private val ethree = appvirgil ethree 2 private lateinit var usercard card 3 private val rxethree = rxethree(context) ajouter une fonction qui obtient une carte virgil du destinataire 1 fun requestcard(identity string, 2 onsuccess (card) > unit, 3 onerror (throwable) > unit) { 4 5 val disposable = rxethree findcard(identity) 6 subscribeon(schedulers io()) 7 observeon(androidschedulers mainthread()) 8 subscribeby( 9 onsuccess = { 10 usercard = it 11 onsuccess(it) 12 }, 13 onerror = { 14 onerror(it) 15 } 16 ) 17 18 compositedisposable += disposable 19 } ajouter le chiffrement des messages sortants dans requestsendmessage requestsendmessage fonction 1 fun requestsendmessage(text string, 2 thread chatthread, 3 onsuccess () > unit, 4 onerror (throwable) > unit) { 5 6 val encryptedtext = ethree authencrypt(text, usercard) 7 val disposable = rxparse sendmessage(encryptedtext, thread) 8 9 } ajouter le déchiffrement de tous les messages entrants dans chatthreadrvadapter chatthreadrvadapter classe (dans /virgilsecurity/virgilback4app/chat/thread/) /virgilsecurity/virgilback4app/chat/thread/) ajouter des champs 1 private var ethree ethree = appvirgil ethree 2 lateinit var interlocutorcard card implémenter le déchiffrement des messages dans onbindviewholder onbindviewholder fonction 1 override fun onbindviewholder(viewholder recyclerview\ viewholder, position int) { 2 when (viewholder) { 3 is holdermessageme > { 4 val decryptedtext = ethree authdecrypt(items\[position] body) 5 viewholder bind(decryptedtext) 6 } 7 is holdermessageyou > { 8 val decryptedtext = ethree authdecrypt(items\[position] body, interlocutorcard) 9 viewholder bind(decryptedtext) 10 } 11 } 12 } 8 exécutez la démonstration complète de bout en bout chiffrée maintenant, pour voir le résultat de notre démo entièrement chiffrée de bout en bout, suivez à nouveau ces étapes déconnectez l'utilisateur précédent inscrivez 2 nouveaux utilisateurs ; commencez une conversation entre eux et envoyez quelques messages ; ouvrez back4app “tableau de bord” > “noyau” > “navigateur de base de données” > “message” important ! vous devez déconnecter l'utilisateur actuel et enregistrer deux nouveaux utilisateurs, après cela vous pouvez commencer le chat e2ee avec ces deux nouveaux utilisateurs la raison est que vos deux premiers utilisateurs n'ont pas de carte virgil carte virgil ’s, donc vous ne pouvez pas utiliser chiffrer\déchiffrer pour eux { blockquote tip} fait ! maintenant, vous pouvez voir que les messages des utilisateurs sont chiffrés et ne peuvent être accessibles dans l'application que par les utilisateurs eux mêmes conformité hipaa & gdpr le chiffrement de bout en bout est un moyen de répondre aux exigences techniques de la hipaa (la loi américaine sur la portabilité et la responsabilité en matière d'assurance maladie de 1996) et du gdpr (le règlement général sur la protection des données de l'union européenne) si vous avez besoin de plus de détails, inscrivez vous pour un compte virgil https //developer virgilsecurity com/account/signup?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat , rejoignez notre communauté slack et contactez nous là bas nous sommes heureux de discuter de vos propres circonstances de confidentialité et de vous aider à comprendre ce qui est nécessaire pour répondre aux exigences techniques de la hipaa et du gdpr que faire ensuite ? projet final https //github com/virgilsecurity/chat back4app android/ si vous avez manqué des pièces du puzzle, ouvrez la branche du projet e2ee vous pouvez insérer vos identifiants d'application dans ce code (comme vous l'avez fait pendant l'article) et construire le projet vous pouvez trouver plus d'informations sur ce que vous pouvez construire avec virgil security ici https //virgilsecurity com/?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat