# Protection en 2-man-rule
Lors de l'étape précédente, nous avions abouti à une version satisfaisante d'un point de vue sécurité.
Néanmoins, cette version a toujours un défaut d'expérience utilisateur : si l'utilisateur perd son mot de passe, il ne peut plus récupérer son identité Seald, et perd donc la capacité de déchiffrer ses données.
Lors de cette étape, nous remplacerons la méthode de protection de l'identité par mot de passe par la méthode de protection en 2-man rule. Cette méthode nécessite l'envoi d'un challenge à l'utilisateur. Dans un premier temps, ce challenge sera envoyé par email, puis nous verrons comment l'envoyer par SMS.
La branche sur laquelle est basée cette étape est
4-signup-jwt
(opens new window)
, le résultat avec le challenge par email
est 5-two-man-rule
(opens new window)
.
L'implémentation avec le challenge par SMS est
6-two-man-rule-sms
(opens new window).
# Explication
Tel qu'expliqué dans le guide dédié, l'objectif du mode de protection en 2-man-rule est de donner aux utilisateurs "le droit" d'oublier leur mot de passe. La contrepartie est que ce mode n'est pas strictement de bout-en-bout.
Son fonctionnement consiste à "couper" en deux l'identité, et à placer une moitié — appelée twoManRuleKey
— sur le backend soumis à authentification, et l'autre moitié — appelée encryptedIdentity
— sur Seald SDK Key Storage (SSKS) soumis à une authentification indépendante par email de confirmation.
Pour implémenter la protection en 2-man-rule il faut :
- modifier le backend pour :
- stocker une
twoManRuleKey
pour chaque utilisateur en base de données ; - exposer un point d'API authentifié pour générer, rengistrer et récupérer la
twoManRuleKey
, et pour que le frontend puisse interagir avec SSKS afin de déposer & récupérer la deuxième moitié de la cléencryptedIdentity
;
- stocker une
- modifier le frontend pour :
- remplacer le plugin de protection par mot de passe par le plugin
@seald-io/sdk-plugin-ssks-2mr
de protection en 2-man-rule ; - gérer l'étape d'authentification indépendante par SSKS.
- remplacer le plugin de protection par mot de passe par le plugin
TIP
Pour une exécution plus rapide, vous pouvez utiliser une rawTwoManRuleKey
à la place de la twoManRuleKey
. Pour plus de détails, allez voir le paragraphe "Utilisation d'une TwoManRule Key brute" du guide sur les identités.
# Modification du backend
Nous allons dans l'ordre :
- modifier le modèle
User
pour pouvoir stocker unetwoManRuleKey
pour chaque utilisateur en base de données ; - ajouter des variables dans le fichier de configuration pour authentifier la connexion du backend à SSKS ;
- exposer un point d'API
sendChallenge2MR
permettant de générer / récupérer latwoManRuleKey
et de créer une session entre le frontend et SSKS afin d'y déposer /récupérer la deuxième moitié de la cléencryptedIdentity
.
# Modification du modèle User
Dans le fichier backend/models.js
, on ajoute à l'initialisation du modèle User
la propriété twoManRuleKey
:
# Fichier de configuration
On ajoute deux variables dans le fichier de configuration du backend :
Nom | Valeur par défaut | Obligatoire | Description |
---|---|---|---|
KEY_STORAGE_URL | undefined | Oui | URL de SSKS, disponible sur le tableau d'administration Seald |
KEY_STORAGE_URL_APP_KEY | undefined | Oui | Clé d'API ssksAppKey , disponible sur le tableau d'administration Seald |
Pour savoir où trouver ces valeurs, référez-vous au paragraphe en question dans le guide concernant l'intégration du 2-man-rule.
# Point d'API sendChallenge2MR
Ce point d'API fait en une requête les deux éléments décrits dans le guide d'intégration dédié :
- il génère & stocke si besoin, puis rend la
twoManRuleKey
associée à un utilisateur de votre application ; - il utilise l'API de SSKS pour que l'utilisateur puisse s'authentifier auprès de SSKS.
Dans le fichier backend/routes/account.js
, on va ajouter une route POST authentifiée '/sendChallenge2MR'
.
Pour interagir avec SSKS on va utiliser node-fetch
, il faut donc l'installer :
cd backend
npm install node-fetch
Puis l'importer dans le fichier backend/routes/account.js
:
Enfin, on crée la route /sendChallenge2MR
qui va :
- ordonner à SSKS, si une authentification de l'utilisateur est nécessaire, d'envoyer un email au format du
template
à l'adresse email de l'utilisateur qui contiendra unchallenge
(valable 6h) que l'utilisateur renverra à SSKS dans la session identifiée par letwoManRuleSessionId
; - générer, stocker & rendre la
twoManRuleKey
à l'utilisateur ; - indiquer au frontend si une authentification est nécessaire pour stocker l'identité sur SSKS.
TIP
Il est important de donner un numéro de téléphone ou une adresse e-mail valide, ainsi qu'un template
contenant $$CHALLENGE$$
de façon visible, étant donné que SSKS va remplacer $$CHALLENGE$$
par un challenge aléatoire que l'utilisateur devra recopier.
Désormais, nous avons fait toutes les modifications qu'il fallait effectuer sur le backend, passons au frontend.
# Modification du frontend
Nous allons dans l'ordre :
- modifier le client API
frontend/src/services/api.js
pour y intégrer ce nouveau point d'API, et supprimer la pré-dérivation du mot de passe ajoutée précédemment, inutile avec le mode de protection en 2-man-rule ; - modifier le service Seald
frontend/src/services/seald.js
pour modifier les fonctions de création et de récupération d'identité ; - ajouter, si demandé par le backend (si
mustAuthenticate === true
), l'étape d'authentification par email effectuée par SSKS en UI dans leSignIn.jsx
et leSignUp.jsx
.
# Client API
Dans le fichier frontend/src/services/api.js
, on ajoute la route sendChallenge2MR: body => POST('/account/sendChallenge2MR', body)
à l'APIClient
, puis on ajoute une méthode statique sendChallenge2MR
à la classe User
:
Ensuite, on peut supprimer la pré-dérivation du mot de passe dans les méthodes createAccount
et login
.
# Service Seald
# Configuration
On commence par installer le plugin @seald-io/sdk-plugin-ssks-2mr
, supprimer le plugin @seald-io/sdk-plugin-ssks-password
et scyrpt
:
cd frontend
npm install @seald-io/sdk-plugin-ssks-2mr
npm uninstall @seald-io/sdk-plugin-ssks-password scrypt
Puis, dans le fichier frontend/src/services/seald.js
, on l'importe et l'ajoute au SDK. On importe également User
dont on aura besoin par la suite :
# Création et sauvegarde de l'identité
La sauvegarde de l'identité se fait désormais en deux étapes :
- la demande d'envoi du challenge ;
- la sauvegarde de l'identité sur SSKS, avec ou sans challenge selon la valeur de
mustAuthenticate
.
Pour cela, on modifie la fonction createIdentity
qui n'a plus besoin du password
comme argument, et qui, au lieu de sauvegarder l'identité par mot de passe, demande l'envoi d'un challenge via le point d'API créé ci-dessus :
Cette fonction retourne un objet contenant twoManRuleKey
, twoManRuleSessionId
(pour éviter toute confusion avec le sessionID
de l'authentification du client sur le backend), et mustAuthenticate
, envoyés par le backend.
Si mustAuthenticate
est true
, SSKS déclenche l'envoi d'un challenge
par email ou par SMS. Ce challenge
est valable 6h qui devra être recopié par l'utilisateur.
Si mustAuthenticate
est false
, la sauvegarde de l'identité sur SSKS peut se faire sans challenge
.
On crée une fonction saveIdentity2MR
qui permettra d'enregistrer l'identité sur SSKS :
Une fois cette fonction appelée, l'identité sera sauvegardée sur SSKS.
# Récupération de l'identité
Lors de la récupération d'une identité, l'authentification auprès de SSKS est obligatoire.
La récupération de l'identité se fait désormais en deux étapes :
- l'envoi du challenge ;
- l'utilisation du challenge pour récupérer l'identité depuis SSKS.
Tout d'abord on ajoute une fonction sendChallenge2MR
qui fait juste un simple appel au point d'API précédemment créé sur le backend :
Ensuite, on modifie fonction retrieveIdentity
en retrieveIdentity2MR
qui ne prend plus password
en argument, mais prend comme nouveaux arguemtns :
emailAddress
: adresse e-mail de l'utilisateur ;twoManRuleKey
: clé stockée par le backend ;twoManRuleSessionId
: identifiant de la session créée par le backend pour l'utilisateur auprès de SSKS ;challenge
: challenge aléatoire envoyé par e-mail.
Et on change simplement le plugin utilisé pour appeler la fonction sealdSDKInstance.ssks2MR.retrieveIdentity
avec ces arguments :
Après avoir appelé cette fonction, l'identité sera récupérée et le SDK sera utilisable.
WARNING
Il est déconseillé de récupérer la même identité avec ssks2MR.retrieveIdentity
sur plusieurs appareils en même temps, au même instant exact, par exemple lors
de tests automatisés. Veuillez attendre que l'un des appareils ait fini de
récupérer l'identité avant de lancer la récupération sur un autre appareil.
# Modification de l'interface utilisateur
Il faut désormais appeler ces fonctions :
- à la création de compte ;
- à la connexion.
# Création de compte SignUp.jsx
On commence par rajouter deux hooks d'état :
Puis, on ajoute un composant ChallengeForm
qui est un formulaire permettant à l'utilisateur, lorsque mustAuthenticate
est true
, de taper le challenge
et qui appelle handleChallengeSubmit
lorsque le formulaire est validé :
Ce ChallengeForm
sera utilisé à la place du SignupForm
si challengeSession
est défini :
Ensuite, on modifie la fin du handleSignupSubmit
pour qu'elle affiche à l'utilisateur le ChallengeForm
si mustAuthenticate
est true
, et pour qu'elle enregistre directement l'identité sinon :
Enfin, on crée handleChallengeSubmit
qui utilise le challenge
complété par l'utilisateur pour sauvegarder son identité sur SSKS :
Ça y est, nous avons mis en œuvre toutes les modifications dans le frontend.
# 2-man-rule par SMS
Une fois le two-man-rule avec un challenge par email implémenté, il est très facile de passer au challenge par SMS.
Il va falloir changer le frontend pour demander un numéro de téléphone à l'utilisateur, puis l'enregistrer dans la base de données coté backend.
Nous allons ensuite effectuer des modifications mineures à l'utilisation du plugin @seald-io/sdk-plugin-ssks-2mr
.
# Modification du modèle User
en backend
Dans le fichier backend/models.js
, on ajoute à l'initialisation du modèle User
la propriété phoneNumber
:
# Modification du point d'API et du validateur associé
Il faut ajouter un champ phoneNumber
dans le validateur de requête :
Le champ phoneNumber
doit ensuite être récupéré et transmis au modèle :
# Modification du point d'API sendChallenge2MR
Les modifications ici sont simples. Il faut modifier le auth_factor
. Son type
passe de 'EM'
à 'SMS'
, et sa value
change pour contenir le numéro de téléphone.
Deuxièmement, il faut modifier le champ template
. Celui ne contient plus le code HTML de l'email, mais le texte du SMS. Là encore, le template doit contenir la clé $$CHALLENGE$$
qui sera remplacée par le challenge.
# Modification coté frontend
Les modifications coté frontend sont très similaires à celle du backend. Il va falloir :
- Ajouter un champ
numéro de téléphone
dans leSignupForm
. - Ajouter une clé
phoneNumber
dans le modèle utilisateur. - Modifier le point d'API de création de compte et son appel pour inclure le numéro de téléphone.
Une fois ces trois modifications effectuées, il faut changer les fonctions saveIdentity2MR
et retrieveIdentity2MR
dans le fichier seald.js
.
Il faut ajouter un argument phoneNumber
à ces fonctions, puis modifier l'argument authFactor
lors des appels aux fonctions du plugin.
Là encore, le type du facteur d'authentification devient 'SMS'
, et sa valeur devient le numéro de téléphone.
# Conclusion
Nous avons pu mettre en oeuvre la protection en 2-man-rule complètement, des améliorations peuvent encore être apportées, par exemple au lieu de demander à l'utilisateur de recopier le challenge
il est possible de l'inclure dans un magic link vers l'application (attention à ne pas utiliser de variable GET mais un fragment d'URL pour que le serveur ne connaisse pas le challenge
).