https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-a-one-time-password-otp-service-using-the-dispatch-api/onetimepassword.png

Construire un service de mot de passe à usage unique (OTP) en utilisant l'API Dispatch

Publié le October 23, 2020

Temps de lecture : 13 minutes

Avis d'obsolescence du produit

À partir du 31 août 2025, l'API Dispatch de Vonage sera fermée aux nouveaux utilisateurs, bien que le produit continue d'être pris en charge par les utilisateurs existants. Si vous souhaitez créer une application de messagerie avec une fonctionnalité de basculement, le basculement est désormais pris en charge directement dans l'API Messages.

Pour des informations générales sur la fonction de basculement des messages, veuillez vous référer à ce guide. Pour des conseils sur la migration de Dispatch API vers Messages API Failover, veuillez vous référer à ce guide.

Si vous avez d'autres questions concernant l'obsolescence de ce produit, veuillez nous nous contacter sur le Vonage Community Slack.

Les mots de passe à usage unique (OTP) sont devenus assez familiers ces derniers temps, principalement en raison d'une exigence de sécurité que les mots de passe traditionnels ne garantissent pas. Alors que la protection du mot de passe traditionnel relève de la responsabilité de l'utilisateur qui, comme nous le savons tous, ne s'en soucie pas suffisamment, le mot de passe à usage unique est pratiquement autoprotégé car il est généré de manière aléatoire et sa validité est limitée dans le temps.

Vous pouvez utiliser les OTP à la place des mots de passe traditionnels ou pour renforcer le processus d'authentification traditionnel avec une approche d'authentification à deux facteurs (2FA). En fait, vous pouvez utiliser les OTP partout où vous avez besoin d'un mécanisme qui garantit l'identité d'un utilisateur en s'appuyant sur un moyen de communication dont il est propriétaire : une boîte aux lettres, un téléphone, une application spécifique, etc.

Dans cet article, nous verrons comment mettre en œuvre un service OTP de base basé sur deux API Web :

  • la première API permet de créer l'OTP et de l'envoyer à l'utilisateur via Facebook Messenger comme moyen principal, ou via SMS comme moyen de repli.

  • la seconde API permet à l'utilisateur de vérifier l'OTP qu'il a reçu

Le service OTP n'a pas d'interface utilisateur. Il est conçu comme un microservice que vous pouvez invoquer depuis votre application pour générer et vérifier les OTP.

Conditions préalables

Pour utiliser le service OTP présenté dans cet article, vous devez :

  • Node.js 8.11+ installé sur votre ordinateur

  • A Messenger et un téléphone permettant de recevoir des SMS.

  • Une application pour envoyer des requêtes HTTP, telle que curl ou Postman

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

Mise en place du projet

Dans un premier temps, vous devez cloner ou télécharger le projet depuis le dépôt GitHub.

Une fois que vous avez le code du projet sur votre ordinateur, vous devez installer ses dépendances en vous déplaçant dans le dossier du projet et en tapant la commande suivante :

npm install

Comme nous le verrons plus tard, l'application utilise Express comme cadre web et la bibliothèque client Vonage pour Node.js, afin d'envoyer l'OTP à l'utilisateur.

Configurer l'Applications

Avant d'utiliser le service OTP, vous devez effectuer certaines configurations sur le tableau de bord de l'API de Vonage afin d'activer la livraison des messages par l'intermédiaire de l'API de distribution de Vonage. Dispatch API de Vonage.

Cette API vous permet d'envoyer des messages à vos utilisateurs en utilisant plusieurs canaux avec une priorisation. Par exemple, dans notre cas, nous enverrons l'OTP à l'utilisateur via son Account Messenger, en guise de première tentative. Si l'utilisateur ne le lit pas dans un délai donné, le message sera envoyé par SMS à son numéro de téléphone.

Accédez donc au tableau de bord de votre API Vonage et sélectionnez l'élément Messages et Dispatch dans le menu, puis sélectionnez Créer une Applicationscomme le montre l'image ci-dessous :

Vonage DashboardVonage Dashboard

Dans le jargon de l'API de Vonage, une application est un ensemble de données vous permettant d'utiliser l'API Messages et Dispatch. Comme nous pouvons le voir dans le formulaire ci-dessus, les données minimales sont les suivantes :

  • le nom de l'application

  • l'URL d'un webhook public activé pour recevoir l'état de livraison des messages

  • l'URL d'un webhook public habilité à recevoir des messages entrants

  • la clé publique utilisée pour signer la demande que vous envoyez à l'API (vous pouvez générer une paire de clés publique et privée en cliquant sur le lien situé sous la zone de texte)

Les étapes deux et trois du processus de création de l'application de messages vous permettent d'attribuer des numéros de téléphone et/ou des liens vers des comptes externes pour des services tels que Messenger, WhatsApp ou Viber afin que vous puissiez envoyer des SMS ou des messages par l'intermédiaire de l'API Message et Dispatch.

En particulier, voir ce document pour comprendre comment lier votre page Facebook à votre compte API Vonage.

Sachez que l'état et les URL entrantes à fournir à une application API de Vonage doivent être accessibles au public. Si vous n'avez pas de serveur web public ou si vous voulez simplement essayer ceci sur votre ordinateur, utilisez ngrokun outil qui vous permet d'exposer publiquement votre serveur web local.

Vous trouverez plus de détails sur la façon de travailler avec Ngrok dans notre documentation. Gardez à l'esprit que, si vous utilisez le plan gratuit de ngrok, une URL temporaire sera générée à chaque fois que vous lancerez l'outil. Vous devez donc mettre à jour les URL de l'application dans les paramètres de votre tableau de bord en conséquence.

Après avoir créé votre application Vonage API, un numéro de téléphone Application ID lui sera attribué. Prenez-en note.

Configurer le service OTP

Une fois que vous avez configuré le côté Vonage, vous devez configurer le côté service OTP afin de les faire communiquer l'un avec l'autre.

Ouvrez donc le fichier nexmo.json sous le dossier src du projet et fournissez les données demandées :

{
   "apiKey": "YOUR_API_KEY",
   "apiSecret": "YOUR_API_SECRET",
   "applicationId": "YOUR_APPLICATION_ID"
}

Vous pouvez récupérer le apiKey et le apiSecret dans la section des paramètres du tableau de bord de l'API de Vonage, tandis que la valeur applicationId est la valeur dont vous avez pris note dans la section précédente.

Ensuite, prenez la clé privée associée à la clé publique que vous avez attribuée à l'application et stockez-la dans le fichier private.key dans le dossier src.

Exécuter le service OTP

Il est temps d'exécuter votre service OTP. Tapez la commande suivante dans le dossier racine du projet :

npm start

Après quelques instants, vous devriez obtenir un message indiquant que le serveur fonctionne sur le port 3000. Vous pouvez vérifier qu'il fonctionne en pointant un navigateur vers l'adresse http://localhost:3000 à l'aide d'un navigateur. Si tout va bien, vous devriez voir apparaître le message "This is the OTP service".

Demander un OTP

Imaginons maintenant que votre application doive générer un OTP à envoyer à un utilisateur afin de vérifier son identité. Elle doit soumettre une POST au service OTP en fournissant une chaîne de caractères servant d'identifiant à votre demande et aux contacts de l'utilisateur à qui l'OTP doit être envoyé.

Vous pouvez le faire en soumettant une requête HTTP comme la suivante au service OTP en cours d'exécution :

POST /otp/123456789 HTTP/1.1
Host: localhost:3000
Content-Type: application/json
cache-control: no-cache
{"messengerId": "8192836451", "phoneNumber": "393331234567"}

La chaîne 123456789 attachée à l'URI de l'API est l'identifiant de votre demande. Nous la nommons tokenet c'est à vous de la fournir. Le corps de la demande contient un objet JSON avec l'identifiant Messenger et le numéro de téléphone de l'utilisateur qui recevra l'OTP.

Vous pouvez soumettre la demande via Postman comme le montre l'image suivante :

PostmanPostman

Le service OTP génère un OTP composé de 5 chiffres et l'envoie à l'identifiant Messenger spécifié. Comme nous le verrons plus tard, si l'utilisateur ne le lit pas dans un délai donné, l'OTP sera envoyé par SMS au numéro de téléphone.

Après la création réussie d'un OTP, vous devriez recevoir un code d'état HTTP 201 Created.

Verify an OTP

Quel que soit le moyen par lequel le message a été reçu, l'utilisateur doit vérifier l'OTP en soumettant une demande GET à la deuxième API, comme dans l'exemple suivant :

GET /otp/123456789/63731 HTTP/1.1
Host: localhost:3000
cache-control: no-cache

L'URI de l'API est composé de l'élément otp du jeton de requête (c'est-à-dire l'identifiant de requête fourni lors de la demande de création d'un OTP) et de l'OTP lui-même.

Dans Postman, il apparaît comme suit :

PostmanPostman

En envoyant une telle demande, vous pouvez obtenir l'un des codes d'état HTTP suivants en guise de réponse :

  • 200 OK - Vous obtenez cette réponse lorsque votre OTP est valide

  • 404 Non trouvé - Vous obtenez cette réponse lorsque votre OTP est erroné, c'est-à-dire qu'il n'a pas été généré par le service OTP.

  • 409 Le code a déjà été vérifié - Cette réponse signifie que vous ou quelqu'un d'autre a déjà vérifié l'OTP.

  • 410 Le code est expiré - Vous obtenez cette réponse si vous essayez de vérifier un OTP après sa durée de validité.

Vous pouvez également recevoir le message 404 Le code est invalide pour une raison inconnue Code d'état HTTP lorsque le service OTP n'est pas en mesure de vérifier votre code pour une autre raison.

Comment cela fonctionne-t-il ?

Examinons maintenant le code qui met en œuvre notre service OTP. L'image suivante résume les dossiers et les fichiers appartenant au projet :

Project structureProject structure

Le dossier index.js sous le dossier src contient le code de départ de l'application et la définition des Applications Web. Les API de création et de vérification sont mises en œuvre par le code suivant :

app.post("/otp/:token", (req, res) => {
  const otp = otpManager.create(req.params.token);
  otpSender.send(otp, req.body);
  res.sendStatus(201);
 });

 app.get("/otp/:token/:code", (req, res) => {
    const verificationResults = otpManager.VerificationResults;
    const verificationResult = otpManager.verify(req.params.token, req.params.code);
    let statusCode;
    let bodyMessage;

    switch (verificationResult) {
      case verificationResults.valid:
        statusCode = 200;
        bodyMessage = "OK";
        break;
      case verificationResults.notValid:
        statusCode = 404;
        bodyMessage = "Not found"
        break;
      case verificationResults.checked:
        statusCode = 409;
        bodyMessage = "The code has already been verified";
        break;
      case verificationResults.expired:
        statusCode = 410;
        bodyMessage = "The code is expired";
        break;
      default:
        statusCode = 404;
        bodyMessage = "The code is invalid for unknown reason";
  }
  res.status(statusCode).send(bodyMessage);
});

Comme vous pouvez le constater, les deux API s'appuient sur l'API otpManager pour créer et vérifier l'OTP, et sur l'API otpSender pour l'envoyer à l'utilisateur. Leur initialisation a lieu quelques lignes plus haut, dans le même fichier index.js fichier :

const OtpManager = require("./OtpManager");
const otpRepository = require("./otpRepository");
const otpSender = require("./otpSender")

const otpManager = new OtpManager(otpRepository, {otpLength: 5, validityTime: 5});

Vous pouvez voir ici que l'ensemble du service est composé de trois éléments :

  • otpManager Responsable de la création et de la vérification de l'OTP

  • otpRepository Responsable de la persistance de l'OTP

  • otpSender Responsable de l'envoi de l'OTP à l'utilisateur

L'existence de ces trois composants vous permet de maintenir la mise en œuvre de la création et de la vérification, du stockage et de la livraison indépendante l'une de l'autre.

Lorsque vous créez l'instance de otpManagervous passez l'élément otpRepository et un objet options indiquant la longueur de l'OTP (cinq caractères) et la durée pendant laquelle il doit être considéré comme valide (cinq minutes).

Le gestionnaire otpManager

Le otpManager est une instance de la classe OtpManager implémentée dans le fichier OtpManager.js dans le fichier Ses principales méthodes sont create() et verify().

La méthode create() génère un nouvel OTP et est mise en œuvre comme suit :

create(token) {
  const code = Math.floor(Math.random()*Math.pow(10, this.options.otpLength))
    .toString()
    .padStart(this.options.otpLength, "0");

  let otp = new OtpItem(token, code);
  this.otpRepository.add(otp);

  return otp;
}

Il prend un jeton en entrée et génère un nombre aléatoire de cinq chiffres. Il s'assure que le code résultant est composé d'exactement cinq chiffres, même si le premier chiffre est un zéro, en le convertissant en une chaîne de caractères et en le complétant par des caractères "0".

Bien entendu, il s'agit d'une mise en œuvre très simple de la création d'OTP. Vous pourriez vouloir mettre en œuvre des algorithmes plus précismais cela sort du cadre de cet article.

Une fois le code généré, il crée un objet otp en tant qu'instance de la classe OtpItem et ajoute la nouvelle instance otp à la base de données otpRepository.

La classe OtpItem définit la structure permettant de représenter les informations pertinentes pour l'OTP et est mise en œuvre dans le fichier OtpItem.js fichier :

class OtpItem {
  constructor(token, code) {
    this.token = token;
    this.code = code;
    this.creationDate = new Date();
    this.isChecked = false;
    this.checkDate = null;
  }
}

La méthode verify() vérifie si le code passé pour un jeton donné a été généré et s'il est toujours valide. Voici son implémentation :

verify(token, code) {
    const id = `${token}-${code}`;
    const otp = this.otpRepository.getById(id);
    let verificationResult = VerificationResults.notValid;
  
    if (otp) {
      switch (true) {
        case otp.isChecked:
          verificationResult = VerificationResults.checked;
          break;
        case isOtpExpired(otp, this.options.validityTime):
          verificationResult = VerificationResults.expired;
          break;
        default:
          otp.isChecked = true;
          otp.checkDate = new Date();
          this.otpRepository.update(otp);
          verificationResult = VerificationResults.valid;
  
      }
    }
  
    return verificationResult;
  }
}

La méthode crée un identifiant OTP en concaténant le jeton et le code. Cet identifiant est utilisé pour obtenir l'instance otp de l'instance otpRepository. Si une telle instance existe, la méthode vérifie si elle a déjà été vérifiée et si elle n'a pas expiré. La valeur renvoyée est une valeur énumérée représentant l'état de validité de l'OTP.

Le dépôt otp

L'instance otpRepository stocke l'instance d'un OtpItem dans le système de fichiers sous la forme d'un fichier JSON simple dans le dossier otpItems dans le dossier Il s'agit d'une solution très simple qui fonctionne dans le cadre d'une démonstration. Vous pourriez vouloir la mettre en œuvre en stockant des données dans une base de données.

Voici le code de mise en œuvre que vous trouverez dans le fichier otpRepository.js dans le fichier :

const fs = require("fs");
const path = require("path");

const baseRepositoryPath = "./otpItems";

function add(otpItem) {
  checkBaseFolder();
  fs.writeFileSync(path.join(baseRepositoryPath, `${otpItem.token}-${otpItem.code}`), JSON.stringify(otpItem));
}

function getById(id) {
  const content = getFileContent(path.join(baseRepositoryPath, id));
  let otpItem = null;
  
  if (content) {
    otpItem = JSON.parse(content);
  }

  return otpItem;
}

function update(otpItem) {
    fs.writeFileSync(path.join(baseRepositoryPath, `${otpItem.token}-${otpItem.code}`), JSON.stringify(otpItem));

    return otpItem;
}

function checkBaseFolder() {
  if (!fs.existsSync(baseRepositoryPath)){
    fs.mkdirSync(baseRepositoryPath);
  }
}

function getFileContent(fileName) {
  let content = null;
  
  try {
    content = fs.readFileSync(fileName);
  } catch (error) {
    console.log(error);
  }

  return content;
}

module.exports = {
  getById,
  add,
  update
};

Comme vous pouvez le voir, il implémente la méthode getById() pour récupérer une instance OtpItem instance, la méthode add() pour stocker une instance OtpItem et une méthode update() pour la mettre à jour.

L'expéditeur otp

Le composant otpSender envoie l'OTP à l'utilisateur en utilisant l'API Dispatch de Vonage. Il est implémenté par le fichier otpSender.js comme suit :

const Nexmo = require('nexmo')
const nexmoConfig =require("./nexmo.json");
const path = require("path");

nexmoConfig.privateKey = path.join(__dirname, "private.key");

const nexmo = new Nexmo(nexmoConfig);

function send(otp, recipientAdresses) {
  const message = `Insert the following code: ${otp.code}`;

  nexmo.dispatch.create("failover", [
    {
      "from": { "type": "messenger", "id": "YOUR_MESSENGER_ID" },
      "to": { "type": "messenger", "id": recipientAdresses.messengerId },
      "message": {
        "content": {
          "type": "text",
          "text": message
        }
      },
      "failover":{
        "expiry_time": 120,
        "condition_status": "read"
      }
    },
    {
      "from": {"type": "sms", "number": "NEXMO"},
      "to": { "type": "sms", "number": recipientAdresses.phoneNumber},
      "message": {
        "content": {
          "type": "text",
          "text": message
        }
      }
    },
    (err, data) => {
      console.log(data.dispatch_uuid);
    }
  ])  
}

module.exports = {
  send
};

Il compose une configuration en fusionnant les données du fichier nexmo.json et du fichier private.key et du fichier Cette configuration est transmise au constructeur de la bibliothèque afin d'obtenir une instance de nexmo instance. Cette instance sera utilisée dans l'implémentation de la fonction send() . Cette instance sera utilisée dans l'implémentation de la fonction La fonction prend une OtpItem et un objet recipientAddresses comme arguments et construit le message à envoyer à l'utilisateur et la charge utile pour la fonction Dispatch API

Grâce à la méthode nexmo.dispatch.create() vous créez un flux de livraison avec basculement. Le deuxième argument de la méthode est un tableau contenant trois éléments :

  • Le premier élément est un objet spécifiant l'expéditeur, le destinataire et le texte du message à envoyer. Le type de l'expéditeur et du destinataire indique qu'il s'agit d'une communication par messagerie. L'objet possède également une propriété failover qui spécifie à quel moment la livraison doit être considérée comme un échec. Dans notre cas, elle est considérée comme échouée si le message n'est pas lu dans les 120 secondes.

  • Le deuxième élément est un autre objet spécifiant l'expéditeur, la livraison et le texte du message à envoyer en cas d'échec de la livraison par Messenger. Dans ce cas, le type de l'expéditeur et du destinataire indique que le message doit être envoyé par SMS.

  • Le dernier élément est une fonction de rappel exécutée après que le flux de travail a été soumis au serveur API de Vonage. Dans notre cas, nous écrivons simplement dans la console l'identifiant du flux de travail (dispatch_uuid) renvoyé par Vonage.

Cela augmente les chances que l'OTP généré soit remis à l'utilisateur, quel que soit le moyen de communication utilisé.

Partager:

https://a.storyblok.com/f/270183/384x384/373925f138/andrea-chiarelli.png
Andrea Chiarelli

Andrea Chiarelli has over 20 years of experience as a software engineer and technical writer. Throughout his career, he has used various technologies for the projects he was involved in. Currently, he is a software architect at the Italian office of Apparound and contributes to a few online magazines and blogs.