Comment mettre en place un système de double authentification par SMS ?

Un système de double authentification permet d'ajouter une couche de vérification quant à l'identité d'un utilisateur souhaitant se connecter à une plateforme ou une application. Il peut être vu comme un fardeau par les utilisateurs car il nécessite de réaliser une étape supplémentaire pour accéder au contenu souhaité, mais dans certains cas il est essentiel pour garantir la sécurité des données des utilisateurs. La double authentification rend aussi plus difficile l'automatisation de la connexion par un robot, réduisant les chances de spam et de fraudes. Considérée comme une méthode d'authentification forte, la double authentification oblige vos utilisateurs à fournir deux preuves d'identité distinctes. On rencontre bien souvent cette méthode d'authentification, sans même s'en rendre compte, avec le paiement par carte bancaire. En effet, payer avec votre carte bancaire en saisissant votre code secret est une forme de double authentification, la première preuve étant la possession de la carte bancaire et la deuxième, le code secret.
Il fut un temps où la majorité des authentifications à deux facteurs (A2F) se faisaient par email. S'authentifier par email peut être rébarbatif pour l'utilisateur : devoir ouvrir sa boîte mail, attendre que le mail arrive, l'ouvrir, puis copier-coller le code ou cliquer sur un lien peut en décourager plus d'un. De plus, une boîte mail peut être ouverte en même temps sur une multitude d'appareils différents, augmentant le nombre de vecteurs d'intrusion possibles.
Comme vous l'avez sûrement deviné grâce au titre de cet article, nous allons voir comment mettre en place un système d'A2F en utilisant des SMS. Le principe est que l'utilisateur confirme son authentification en entrant un code qu'il reçoit par SMS , appelé One Time Password (OTP). Ce moyen de double authentification est plus user friendly, surtout avec la possibilité ajoutée depuis la version d'iOS 12 de remplir automatiquement un champ avec le code reçu par SMS.
Dans la suite de cet article, nous allons voir comment nous pouvons mettre en place une double authentification par SMS sur une application en React Native.

Flow

Afin d'intégrer ce système de double authentification, intéressons-nous d'abord à l'enchaînement des événements.
Sur notre application, quand un utilisateur va essayer de se connecter, nous allons générer un OTP et le sauvegarder dans une base de données en y indiquant une date d'expiration. En utilisant une API, nous allons ensuite envoyer ce code par SMS à l'utilisateur, qu'il pourra alors saisir pour mettre fin à l'authentification.
Schéma des échanges entre le frontend, le backend et l'API de SMS

Prérequis

Dans la suite de l'article, nous supposons qu'un système d'authentification avec login et mot de passe existe, et nous expliquons comment y intégrer une vérification par SMS. Nous supposons aussi que le numéro de téléphone des utilisateurs est enregistré en base de données.
Les technologies suivantes sont utilisées :

Service d'envoi de SMS

Afin d'envoyer les SMS contenant les codes de vérification, nous allons utiliser le service Twilio.
Twilio met également à disposition Verify, qui est une API tout-en-un permettant de gérer l'envoi de code de vérification. Dans cet article, afin que la méthode présentée soit applicable à tout service d'envoi de SMS, nous allons utiliser l'API d'envoi de SMS classique de Twilio.
Par défaut, un numéro d'expédition est affiché au destinateur du SMS. Si vous souhaitez que l'expéditeur soit affiché au format alphanumérique, vous pouvez consulter cet article.
Sur l'écran de droite, le nom de l'expéditeur est au format alphanumérique.

Création et envoi du OTP

Une fois que l'utilisateur a entré ses identifiants et que l'application a vérifié qu'ils étaient corrects, nous générons un OTP en utilisant la librairie generate-sms-verification-code :
import * as phoneToken from 'generate-sms-verification-code' const generateUserVerificationCode = () => { return phoneToken(4); }
Ici, un code de 4 chiffres est généré.
Nous inscrivons ensuite le code en base de données avec une date d'expiration :
await this.userRepository.update(user.userId, { smsOTP: verificationCode, otpExpirationDate: moment().add(5, 'm').toDate(), });
Ici, le code expire 5 minutes après sa génération.
Il faut maintenant que nous envoyions le code par SMS à l'utilisateur via Twilio. Pour cela, il est nécessaire d'avoir votre Twilio Account SID et votre Twilio Auth Token, tous deux accessibles depuis votre console Twilio.
import * as twilio from 'twilio'; const accountSid = process.env.TWILIO_ACCOUNT_SID; // Your Account SID from www.twilio.com/console const authToken = process.env.TWILIO_AUTH_TOKEN; // Your Auth Token from www.twilio.com/console const client = twilio(accountSid, authToken); async sendSMSToUser(user: User, body: string) { const smsSent = await client.messages.create({ body, to: user.phoneNumber, from: process.env.TWILIO_PHONE_NUMBER, }); return !!smsSent.sid; }
Le code ci-dessus crée un client Twilio utilisé pour appeler l'API d'envoi de SMS avec la méthode create de l'objet messages. Vous pouvez trouver le code complet de la génération et de l'envoi du code intégré à un système d'authentification ici.

Entrée du code de vérification

Maintenant que vos utilisateurs reçoivent un code lorsqu'ils essayent de se connecter, ils doivent pouvoir le renseigner dans votre application afin de finaliser l'authentification. Pour cela nous utilisons la librairie react-native-confirmation-code-field, qui permet de créer des champs de textes spécifiquement faits pour les codes.
Exemple d'écran codé en React Native permettant d'insérer un code
Cette librairie nous laisse la possibilité d'appeler une fonction automatiquement quand le code de vérification a fini d'être saisi (grâce à la propriété onFulfill), ainsi que de remplir automatiquement le code envoyé par SMS sur iOS.
Dans notre code, nous avons nommé cette fonction onCodeInputFulfilled : elle envoie le code entré par l'utilisateur au backend, qui lui renvoie une réponse au frontend indiquant si le code est valide ou non, avec une erreur le cas échéant. En fonction du résultat, l'utilisateur est redirigé vers un écran de succès, ou un message d'erreur lui est affiché.
const onCodeInputFulfilled = (value: string) => { setIsProcessing(true); submitVerificationCode(value, route.params.userId).then((res) => { setIsProcessing(false); if (res.success) { navigation.navigate('SuccessScreen'); } else { setErrorMessage(res.errorMessage); } }); };

Vérification du code

Voici le code backend permettant de vérifier si le code envoyé par SMS est valide :
@Post('/verifyCode') async verifyUserCode(@Body() data, @Res() res) { const verificationResult = await this.userService.verifyUserCode( data.userId, data.verificationCode, ); res.status(HttpStatus.OK).send({ success: verificationResult.success, errorMessage: verificationResult.errorMessage, }); }}
Ici, la fonction verifyUserCode est appelée suite à une requête à la route /verifyCode. Elle appelle autre fonction verifyUserCode présente dans le service NestJS gérant les utilisateurs, que nous avons appelé ici userService :
async verifyUserCode(userId: number, verificationCode: string) { const retrievedUser = await this.userRepository.findOne({ userId }); if (verificationCode !== retrievedUser.smsOTP) { return { success: false, errorMessage: 'Wrong verification code' }; } else if (moment().isAfter(retrievedUser.otpExpirationDate)) { return { success: false, errorMessage: 'Verification code expired' }; } else { return { success: true }; } }
Cette fonction vérifie que le code entré par l'utilisateur et le code généré et enregistré en base de données est le même. Elle vérifie aussi que le code n'a pas expiré.
Le résultat est ensuite envoyé au frontend, avec une erreur le cas échéant.

Conclusion

Bravo ! Vous venez d'apprendre comment intégrer un système de double authentification utilisant un code de vérification envoyé par SMS, avec remplissage automatique du code reçu sur iOS. Nous avons accompli cela en utilisant React Native et NestJS, mais la logique présentée peut s'appliquer à d'autres technologies.
Résumons ce que nous avons vu dans l'article. Pour intégrer à une authentification simple un système d’authentification à deux facteurs par SMS, il faut tout d’abord s’assurer d’être en possession des numéros de téléphone de vos utilisateurs. Au moment où l’utilisateur appuie sur le bouton connexion de votre application, une vérification des identifiants doit être effectuée sur votre backend. S’ils sont corrects, il faut alors générer un OTP, le sauvegarder dans votre base de données en l’associant à l’utilisateur et l’envoyer par SMS à l’aide d'une API d’envoi de SMS. À partir du backend, quand vous avez la confirmation d’envoi du SMS, vous pouvez alors renvoyer une réponse au frontend pour confirmer l’envoi du SMS. Au moment, où l’application reçoit cette confirmation, il faut alors changer l’écran affiché à l’utilisateur et lui laisser la possibilité d’entrer ce OTP tout juste reçu. Quand l’utilisateur a fini de renseigner le code reçu, il faut alors l’envoyer à l’aide d’une requête vers le backend afin de le valider. La validation du OTP se fait en comparant celui stocké en base de données avant l’envoi du SMS avec celui entré sur l’application (tout en vérifiant la date d’expiration). Si le code entré est le bon, l'utilisateur est authentifié et peut être redirigé vers l'écran adéquat.
Afin d'améliorer l'expérience utilisateur, il est conseillé d'afficher un bouton permettant de renvoyer le code s’il n’est pas reçu. Il est préférable de le rendre inactif une fois appuyé pour 15 à 30 secondes, afin d'éviter de très nombreux envois qui peuvent être coûteux et surcharger le backend de l'application.
Voici le code source du système implémenté :
Enfin, voici un GIF déroulant le parcours complet du système d'authentification fraîchement implémenté :

Références

Vous souhaitez être accompagné pour lancer votre projet digital ?
Déposez votre projet dès maintenant
Comment utiliser GitLab CI/CD pour améliorer votre flow de développement ?
Lors du développement d'une application, il y a toujours une petite appréhension lors la mise en production. Cette petite ...
Matthieu Locussol
Matthieu Locussol
Full-Stack Developer @ Galadrim
Comment changer de version de Node.js avec NVM ?
Vous voulez changer rapidement de version de `node` ? nvm est l’outil qu’il vous faut. Pourquoi nvm ? `node` est un exécutable. ...
Florian Yusuf Ali
Florian Yusuf Ali
Full-Stack Developer @ Galadrim
Next.js App Router : le cache et ses dangers
“Il y a seulement 2 problèmes compliqués en informatique : nommer les choses, et l’invalidation de cache”. Phil Karlton. Avec ...
Valentin Gerest
Valentin Gerest
Full-Stack Developer @ Galadrim