Sécurisez votre visioconférence avec des mots de passe à usage unique (OTP)

May 11, 2020
Rédigé par
Révisé par

Sécurisez votre visioconférence avec des mots de passe à usage unique

Comme on respecte consciencieusement la distanciation sociale, les conférences par vidéo en direct deviennent de plus en plus populaires. Des réunions d’entreprise jusqu’aux cours de yoga, en passant par des spectacles de magie, les événements traditionnels “en personne” passent tous au virtuel. Mais bien que la technologie nous connecte, elle s’accompagne aussi de risques de confidentialité et de sécurité.

Ce post vous montrera comment ajouter l’authentification de One-Time Passcodes (mots de passe à usage unique = OTP) à votre application Twilio Video pour garantir que seuls les utilisateurs inscrits sont capables d’accéder à la conférence.

Bien que les mots de passe puissent aider à se protéger du war dialing (la composition intensive de numéro de téléphone), ils ne garantissent pas que les personnes rejoignant la visioconférence sont autorisées à participer. Beaucoup de personnes partagent largement les identifiants et mots de passe des réunions Zoom.

L'authentification par mots de passe à usage unique est utile pour protéger :

Ce tutoriel va vous accompagner étape par étape dans l’ajout de la vérification Twilio Verify SMS à une application Twilio Video en utilisant Python et Flask. Vous pouvez trouver le projet entier sur mon GitHub.

Configuration

Pour suivre ce tutoriel, vous aurez besoin de :

Ce tutoriel s’appuie sur les tutoriels de Twilio Video,  Python et Flask que mon collègue Miguel a écrit la semaine dernière. Vous pouvez les suivre en même temps pour en apprendre plus sur l’API Twilio ou cloner le répertoire complété.

git clone https://github.com/miguelgrinberg/flask-twilio-video.git && cd flask-twilio-video

Voici la différence entre la version Verify et la version de Miguel, si vous avez besoin de référence à un moment. Configurez votre environnement virtuel et téléchargez les prérequis avec la commande suivante :

virtualenv venv
source venv/bin/activate

Créez ensuite un fichier .env. Ce projet a besoin de quelques clés en plus que le modèle de Miguel. Copiez alors le code suivant dans le fichier .env :

# find here: twilio.com/console
TWILIO_ACCOUNT_SID='ACxx...'
TWILIO_AUTH_TOKEN=

# create one here: twilio.com/console/video/project/api-keys
TWILIO_API_KEY_SID=
TWILIO_API_KEY_SECRET=

# create one here: twilio.com/console/verify/services
VERIFY_SERVICE_SID=

# used for session storage
# change this to something difficult to guess!
SECRET_KEY='super-secret'

Les Account SID et Auth Token sont utilisés pour authentifier et instancier le Twilio Client qui servira à envoyer des vérifications. La clé API est utilisée pour l’API vidéo. Votre service Verify contient un set de configurations communes pour envoyer des vérifications comme un nom d’application et une longueur de token.

A ce stade vous avez tout ce dont vous avez besoin pour démarrer la version non-authentifiée de l’app vidéo. Assurez-vous que tout fonctionne correctement en démarrant l’application:

export FLASK_ENV=development && flask run

Naviguez jusqu’à localhost:5000 et vous devriez pouvoir rejoindre la visio-conférence.

capture d'écran de l'application de chat vidéo avec 1 participant en ligne

Comment ajouter l’authentification à vos appels vidéos

Pour joindre l’appel, vous pouvez saisir n’importe quel nom.

Vous voulez vous assurer que vous connaissez et faites confiance aux participants qui rejoignent l’appel, donc vous allez créer une Liste de Contrôle d’Accès (Access Control List, ACL). Ouvrez app.py et ajoutez la fonction suivante :

def get_participant(identity):
   # Hard coded for demo purposes
   # Use your customer DB in production!
   KNOWN_PARTICIPANTS = {
       'blathers': '+18005559876',
       'mabel': '+18005554321',
       'tommy': '+18005556789'
   }
   return KNOWN_PARTICIPANTS.get(identity)

Ajoutez-vous comme participant connu, et assurez-vous que votre numéro de téléphone soit bien au format E.164.

Cela vérifie l’identité que vous avez fournie - dans ce cas un nom d’utilisateur - et l’associe à un canal de contact connu. Vous pouvez vérifier un utilisateur avec ce que vous voulez : un nom, un nom d’utilisateur, un email, un numéro de compte ou n’importe quelle autre information unique.

Cet exemple va utiliser une vérification SMS, donc on renvoie un numéro de téléphone, mais vous pouvez aussi utiliser un email.

Dans votre fonction login, ajoutez la vérification pour un participant connu  suivante :

@app.route('/login', methods=['POST'])
def login():
   username = request.get_json(force=True).get('username')
   if not username:
       abort(401)
  
+   phone_number = get_participant(username)
+   if not phone_number:
+       abort(401)

+   session['username'] = username
  
   token = AccessToken(twilio_account_sid, twilio_api_key_sid,
                       twilio_api_key_secret, identity=username)
   token.add_grant(VideoGrant(room='My Room'))

   return {'token': token.to_jwt().decode()}

Dans cet exemple, on va garder quelques détails dans une session Flask afin de pouvoir les référencer plus tard. Après app = Flask(__name__), ajoutez :

from flask import session
app.secret_key = os.environ.get('SECRET_KEY')

Maintenant votre application ne vous laissera rejoindre l’appel que si vous êtes un participant connu. Testez-la avec un nom au hasard et vous devriez voir un message d’erreur. C’est un progrès, mais quelqu’un pourrait tout de même deviner l’identité de l’un des invités de l’appel.

La vérification par mots de passe à usage unique avec Twilio Verify

Ensuite, vous allez ajouter Twilio Verify à votre application. Ajoutez ce qui suit à app.py :

from twilio.rest import Client
twilio_auth_token = os.environ.get('TWILIO_AUTH_TOKEN')
verify_service_sid = os.environ.get('VERIFY_SERVICE_SID')

def _get_verify_service():
   client = Client(twilio_account_sid, twilio_auth_token)
   return client.verify.services(verify_service_sid)


def start_verification(to):
   service = _get_verify_service()
   service.verifications.create(to=to, channel='sms')


def check_verification(to, code):
   service = _get_verify_service()
   check = service.verification_checks.create(to=to, code=code)
   return check.status == 'approved'

On utilisera les fonctions pour envoyer un mot de passe à usage unique (OTP) au numéro de téléphone du participant et pour vérifier que le code qu’il entre est bien correct. Revenez à la fonction login et ajoutez un appel à start_verification :

def login():
   username = request.get_json(force=True).get('username')
   if not username:
       abort(401)

   phone_number = get_participant(username)
   if not phone_number:
       abort(401)
   session['username'] = username

   start_verification(phone_number)

   token = AccessToken(twilio_account_sid, twilio_api_key_sid,
                       twilio_api_key_secret, identity=username)
   token.add_grant(VideoGrant(room='My Room'))

   return {'token': token.to_jwt().decode()}

Maintenant, si vous rejoignez la visioconférence, vous devriez aussi recevoir un SMS avec le nom de votre service et un mot de passe à usage unique.

capture d'écran du mot de passe à usage unique

Ensuite, vous devez reconfigurer votre code Python pour pouvoir démarrer et vérifier la vérification. Ajoutez une nouvelle route et une fonction pour verify. Là, l’AccessToken (token d’accès) n’est généré que si l’utilisateur a bien confirmé avec l’OTP - le mot de passe à usage unique.

@app.route('/verify', methods=['POST'])
def verify():
   username = session['username']
   phone_number = get_participant(username)
   code = request.get_json(force=True).get('code')
   if not check_verification(phone_number, code):
       abort(401)

   token = AccessToken(twilio_account_sid, twilio_api_key_sid,
                       twilio_api_key_secret, identity=username)
   token.add_grant(VideoGrant(room='My Room'))

   return {'token': token.to_jwt().decode()}

Remplacez les lignes du token AccessToken dans votre fonction login avec une nouvelle instruction de retour. Vous vous en servirez pour dire à l’utilisateur de vérifier son téléphone, pour le code.

def login():
   username = request.get_json(force=True).get('username')
   if not username:
       abort(401)

   phone_number = get_participant(username)
   if not phone_number:
       abort(401)
   session['username'] = username
   session['phone'] = phone_number

   start_verification(phone_number)
-
-   token = AccessToken(twilio_account_sid, twilio_api_key_sid,
-                       twilio_api_key_secret, identity=username)
-   token.add_grant(VideoGrant(room='My Room'))
-
-   return {'token': token.to_jwt().decode()}
+   return {'phone': '********{}'.format(phone_number[-2:])}

Maintenant, il faut mettre à jour le modèle pour ajouter un nouveau champ au formulaire pour le jeton Verify. Ouvrez templates/index.html et ajoutez le formulaire suivant. Vous remarquerez qu’il est caché par défaut.

       <p id="count">Disconnected.</p>
+       <form id="verify" style="display: none;">
+           Code: <input type="text" id="code">
+           <button id="check_code">Verify code</button>
+       </form>
       <div id="container" class="container">

Ensuite, vous avez besoin de mettre à jour votre code JavaScript pour s’occuper du flux de travail de votre vérification. Ouvrez static/app.js et ajoutez les constantes suivantes pour récupérer le formulaire et le code que l’utilisateur a saisi.

const codeInput = document.getElementById('code');
const verifyForm = document.getElementById('verify');

Mettez ensuite à jour le code dans connectButtonHandler pour montrer le formulaire Verify lorsque quelqu’un soumet son nom :

       connect(username).then(() => {
-           button.innerHTML = 'Leave call';
-           button.disabled = false;
+           verifyForm.style.display = "";
       }).catch(() => {
-           alert('Connection failed. Is the backend running?');
+           alert("Error - invalid or unknown user");

Cela met aussi à jour le message d’erreur dans le bloc catch pour indiquer qu’un échec est dû à un utilisateur inconnu. Le reste de cette fonction restera inchangé.

capture d&#x27;écran de l&#x27;application avec le formulaire de code otp mis à jour

Sous le connectButtonHandler, ajoutez un nouveau verifyButtonHandler.

function verifyButtonHandler(event) {
   event.preventDefault();
   const code = codeInput.value;
   verify(code).then(() => {
       verifyForm.style.display = "none";
       button.innerHTML = 'Leave call';
       button.disabled = false;
   }).catch(() => {
       alert("Error - invalid code");
       button.innerHTML = 'Join call';
       button.disabled = false;
   });
};

Remplacez la fonction existante connect par deux autres :

  1. Une fonction actualisée connect qui s’occupe du point de terminaison login. Le point de terminaison login renvoyait habituellement un AccessToken JWT, maintenant il lance le processus de vérification de téléphone. La fonction actualisée connect vous montrera le formulaire Verify et dira à l’utilisateur d’entrer le token.
  2. Une nouvelle fonction verify qui s’occupe du point de terminaison verify. Il confirmera le code de vérification et s’occupera du AccessToken JWT.
function connect(username) {
   var promise = new Promise((resolve, reject) => {
       // start the phone verification process
       fetch('/login', {
           method: 'POST',
           body: JSON.stringify({'username': username})
       }).then(res => res.json()).then(data => {
           count.innerHTML = `Sent code to phone number ${data.phone}. Enter the code to verify.`;
           resolve();
       }).catch(() => {
           reject();
       });
   });
   return promise;
};

function verify(code) {
   var promise = new Promise((resolve, reject) => {
       // get a token from the back end
       fetch('/verify', {
           method: 'POST',
           body: JSON.stringify({'code': code})
       }).then(res => res.json()).then(data => {
           // join video call
           return Twilio.Video.connect(data.token);
       }).then(_room => {
           room = _room;
           room.participants.forEach(participantConnected);
           room.on('participantConnected', participantConnected);
           room.on('participantDisconnected', participantDisconnected);
           connected = true;
           updateParticipantCount();
           resolve();
       }).catch(() => {
           reject();
       });
   });
   return promise;
};

Enfin, ajoutez un event listener en bas de app.js pour gérer de la soumission du formulaire verify.

verifyForm.addEventListener('submit', verifyButtonHandler);

Enregistrez vos fichiers et actualisez ou redémarrez l’application. Vous devriez être le seul à pouvoir rejoindre la visioconférence si vous réussissez avec succès à entrer le code reçu sur votre téléphone. Plutôt cool !

Pour finir

Allez voir la version finie sur mon GitHub ou allez regarder les différences entre ma version avec Verify et l’application vidéo originale de Miguel.

Pour plus d’informations sur la partie vidéo, je vous recommande vivement de jeter un œil à l’article de Miguel. Si vous êtes intéressés par plus de vérification et de sécurité, voici quelques ressources utiles à lire :

Trouvez-moi sur Twitter si vous avez des questions. J’ai hâte de voir ce que vous avez codé !