
Ajouter l'authentification à deux facteurs à votre application Django avec Nexmo
Temps de lecture : 12 minutes
J'ai déjà montré mon amour pour l'authentification à deux facteurs sur le blog de Vonage avec une application de démonstration pour ma entreprise "Kittens & Co".. Il est intéressant de noter que tout le monde n'est pas fan des chats, certains préfèrent les chiens, d'autres préfèrent d'autres animaux, mais nous aimons tous l'authentification à deux facteurs, n'est-ce pas ?
Faisons un petit sondage
Dans ce tutoriel, je vais vous montrer comment ajouter une authentification à deux facteurs à votre site Django en utilisant l'API Verify API de Vonage. Pour ce faire, j'ai construit une petite application appelée "Pollstr" - une simple application web pour faire des sondages. Je sais qu'elle va connaître un succès fulgurant en raison du "e" manquant dans son nom. Je veux ajouter une authentification à deux facteurs pour m'assurer que les gens sont bien ceux qu'ils prétendent être, et pour empêcher le spam sur mes sondages.
Pollstr screenshot
Vous pouvez télécharger le point de départ de l'application à partir de Github et l'exécuter localement.
Visitez ensuite 127.0.0.1:8000 dans votre navigateur et essayez de voter sur un sondage. Vous pouvez vous connecter avec ces informations d'identification :
nom d'utilisateur :
testmot de passe :
test1234
Par défaut, l'application implémente l'enregistrement et la connexion en utilisant le cadre d'authentification intégré de Django, mais la majeure partie de ce tutoriel s'applique de la même manière aux applications qui utilisent d'autres méthodes d'authentification. De plus, nous avons ajouté un peu de bootstrap pour rendre notre application plus jolie.
Tout le code de ce point de départ se trouve sur la page before sur Github. Tout le code que nous ajouterons ci-dessous se trouve sur la branche after sur Github. Pour votre commodité, vous pouvez voir tous les changements entre notre point de départ et notre point d'arrivée sur Github.
Verify de Vonage pour 2FA
Vonage Verify est un moyen sûr et simple de mettre en œuvre la vérification téléphonique en seulement deux appels d'API ! Dans la plupart des systèmes d'authentification à deux facteurs, vous devrez gérer vos propres jetons, l'expiration des jetons, les nouvelles tentatives et l'envoi de SMS. Vonage Verify gère tout cela pour vous.
Pour ajouter Verify à notre application, nous allons effectuer les changements suivants :
Ajouter un
phone_numberà notre utilisateurAjouter un
TwoFactorMixinà nos vues pour s'assurer que l'utilisateur est connecté et vérifiéEnregistrer un nouveau numéro de téléphone pour les nouveaux utilisateurs
Envoyer à l'utilisateur un code de vérification
Verify le code envoyé à leur numéro
Ajout d'un numéro de téléphone
Le modèle d'utilisateur par défaut de Django n'a pas de numéro de téléphone, nous allons donc devoir en ajouter un nous-mêmes. Il y a plusieurs façons de le faire, mais dans ce cas, nous allons garder tout notre nouveau code dans une nouvelle application two_factor app.
Cela générera un grand nombre de nouveaux fichiers dans le dossier /two_factor dans le dossier Ouvrons le fichier /two_factor/models.py et ajoutons un nouveau modèle qui a une relation One-to-One avec notre utilisateur.
# two_factor/models.py
...
from django.contrib.auth.models import User
class TwoFactor(models.Model):
number = models.CharField(max_length=16)
user = models.OneToOneField(User)Ensuite, nous voulons générer les migrations pour ce modèle, mais pour ce faire, nous devons d'abord nous assurer d'ajouter two_factor.apps.TwoFactorConfig à notre fichier INSTALLED_APPS.
# pollstr/settings.py
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'two_factor.apps.TwoFactorConfig',
'django.contrib.admin',
...
]Nous pouvons alors générer nos migrations et migrer notre base de données :
Ajout d'un TwoFactorMixin
Notre application Django utilise des des vues basées sur des classes qui nous permettent d'utiliser des "mixins" personnalisés pour ajouter notre propre comportement à chaque vue. Actuellement, nous utilisons l'élément LoginRequiredMixin pour s'assurer que nous sommes connectés avant de pouvoir voter dans les sondages.
# polls/views.yml
class OptionsView(LoginRequiredMixin, DetailView):
...Nous allons implémenter un nouveau TwoFactorMixin pour ajouter une couche TwoFactor à cette vérification. Commençons par modifier nos vues pour utiliser ce nouveau mixin, même si nous ne l'avons pas encore écrit.
# polls/views.py
from two_factor.mixins import TwoFactorMixin
class OptionsView(TwoFactorMixin, DetailView):
...
class ResultsView(TwoFactorMixin, DetailView):
...
class VoteView(TwoFactorMixin, View):
...Ajoutons maintenant le mixin à notre two_factor app :
# two_factor/mixins.py
from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.urlresolvers import reverse
class TwoFactorMixin(UserPassesTestMixin):
def test_func(self):
user = self.request.user
return (user.is_authenticated and "verified" in self.request.session)
def get_login_url(self):
if (self.request.user.is_authenticated()):
return reverse('two_factor:new')
else:
return reverse('login')Ce que nous avons fait ici, c'est créer un nouveau mixin qui utilise lui-même l'élément UserPassesTestMixin. Ce mixin appelle ensuite automatiquement la fonction test_func où nous vérifions que l'utilisateur est connecté et que cette session a été vérifiée. Pour ce faire, nous vérifions simplement si la clé verified a été définie dans la session. En utilisant la session de cette manière, une personne peut être connectée sur plusieurs machines tout en exigeant une vérification pour chacune d'entre elles.
La fonction get_login fournit à la fonction UserPassesTestMixin une route vers laquelle rediriger l'utilisateur si le test échoue. Dans ce cas, nous avons deux scénarios, l'un où l'utilisateur n'est pas connecté du tout, et l'autre où il est connecté mais non vérifié.
Si vous exécutez votre serveur à ce stade, il échouera parce que nous n'avons pas encore implémenté les routes ou les vues vers lesquelles rediriger l'utilisateur. C'est ce que nous allons faire maintenant.
Sélection d'un numéro de téléphone
Screen Capture of Number Verification Form
Lorsque l'utilisateur a besoin d'être vérifié, il est redirigé vers two_factor:new où nous lui demanderons de définir ou de confirmer le numéro de téléphone auquel nous enverrons un code.
# two_factor/urls.py
from django.conf.urls import url
from . import views
app_name = 'two_factor'
urlpatterns = [
url(r'^$', views.NewView.as_view(), name='new'),
url(r'^create/$', views.CreateView.as_view(), name='create'),
url(r'^verify/$', views.VerifyView.as_view(), name='verify'),
]Nous avons également ajouté les URL de nos prochaines étapes. Ensuite, nous devons nous assurer d'importer ces URLs dans notre application principale.
# pollstr/urls.py
urlpatterns = [
...
url(r'^polls/', include('polls.urls')),
url(r'^2fa/', include('two_factor.urls')),
]Lorsque l'application redirige vers /2fa/ elle essaiera de rendre la vue NewView vue. Cette vue va rendre le TwoFactor disponible pour le modèle, mais nous devons attraper l'exception évidente lorsque l'utilisateur n'a pas encore d'objet TwoFactor et en initialiser un à la place.
# two_factor/views.py
from django.views.generic import DetailView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import TwoFactor
class NewView(LoginRequiredMixin, DetailView):
template_name = 'two_factor/new.html'
def get_object(self):
try:
return self.request.user.twofactor
except TwoFactor.DoesNotExist:
return TwoFactor.objects.create(user=self.request.user)Nous essayons de renvoyer l'enregistrement user.twofactor mais s'il n'existe pas, nous en initialisons un à la place et le renvoyons.
La vue rend le modèle two_factor/new.html qui permet à l'utilisateur soit de saisir son numéro de téléphone, soit d'afficher le numéro qu'il a déjà fourni dans un champ désactivé. Nous ignorerons le numéro dans le champ désactivé plus tard s'il a déjà été défini, mais cela permet de rappeler à l'utilisateur à quel numéro le code sera envoyé.
<!-- two_factor/templates/two_factor/new.html -->
{% extends 'polls/base.html' %}
{% block content %}
<form class='form-inline' action="{% url 'two_factor:create' %}" method="post">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.GET.next }}">
<p>
To continue we need to verify your phone number.
</p>
<div class="form-group">
<input type="text" name="number" value="{{ object.number }}"
{% if object.number %}disabled{% endif %} class='form-control'>
</div>
<div class="form-group">
<input type="submit" name="name" value="Verify" class="btn btn-primary">
</div>
</form>
{% endblock %}
Sans tenir compte de l'overhead de Bootstrap, notre formulaire est un formulaire basique avec quelques champs :
Le
numberpour soumettre un code àLa page
nextvers laquelle rediriger une fois la vérification terminée, c'est une fonctionnalité intégrée de Django, alors jouons-en le jeu.
Lorsque le formulaire est soumis à /2fa/create nous devrons envoyer le code à l'utilisateur avec Vonage.
Utilisation de Vonage Verify
Vonage Verify est très facile à utiliser et se résume essentiellement à deux appels API. Le premier envoie le code de vérification au numéro de téléphone de l'utilisateur. Dans notre cas, cela se produira dans l'API CreateView lorsque le formulaire est soumis.
Pour envoyer le code, nous avons besoin de la bibliothèque vonage Python. Nous l'avons déjà ajoutée à votre requirements.txt ainsi que la bibliothèque django-dotenv qui nous permettra de charger nos informations d'identification à partir d'un fichier .env fichier. Si vous préférez gérer les dépendances de votre application d'une autre manière, vous pouvez les installer directement avec pip.
La bibliothèque vonage peut être instanciée à l'aide d'une clé API et d'un secret, ou en définissant certaines variables d'environnement. Vous pouvez obtenir votre clé et secret API de Vonage dans le tableau de bord du développeur tableau de bord.
Avec ces variables d'environnement définies, nous n'avons plus besoin d'initialiser notre client Vonage et nous pouvons l'utiliser directement comme suit.
# two_factor/views.py
from django.views.generic import DetailView, View
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth import logout
import nexmo
from .models import TwoFactor
class CreateView(LoginRequiredMixin, View):
def post(self, request):
number = self.find_or_set_number(request)
response = self.send_verification_request(request, number)
if (response['status'] == '0'):
request.session['verification_id'] = response['request_id']
return HttpResponseRedirect(reverse('two_factor:verify')+"?next="+request.POST['next'])
else:
logout(request)
messages.add_message(request, messages.INFO, 'Could not verify your number. Please contact support.')
return HttpResponseRedirect('/')
def find_or_set_number(self, request):
two_factor = request.user.twofactor
if (not two_factor.number):
two_factor.number = request.POST['number']
two_factor.save()
return two_factor.number
def send_verification_request(self, request, number):
client = nexmo.Client()
return client.start_verification(number=number, brand='Pollstr')Le code ici fait plusieurs choses. Tout d'abord, il utilise find_or_set_number pour vérifier si l'utilisateur a déjà un numéro de téléphone défini, et seulement si ce n'est pas le cas il enregistre le numéro qu'il a soumis.
Il utilise ensuite nexmo.Client().start_verification pour lancer le processus de vérification. Nous passons ici deux paramètres : le nom de l'utilisateur et un nom d'utilisateur convivial. number de l'utilisateur et un nom convivial brand qui apparaîtra dans le message texte que nous envoyons.
Ensuite, nous vérifions si le status de notre appel API est 0 et si c'est le cas, nous stockons le request_id pour cette tentative de vérification dans la session. Nous faisons cela car nous aurons besoin de ce même id plus tard pour confirmer le code que l'utilisateur a reçu.
Enfin, nous redirigeons l'utilisateur vers notre VerifyView qui est une simple vue qui affiche un formulaire pour saisir le code de vérification.
# two_factor/views.py
from django.views.generic import DetailView, View, TemplateView
class VerifyView(LoginRequiredMixin, TemplateView):
template_name = 'two_factor/verify.html'et le modèle correspondant. Comme vous pouvez le voir, nous transmettons toujours la valeur next afin de pouvoir rediriger vers le bon sondage à la fin.
<!-- two_factor/templates/two_factor/verify.html -->
{% extends 'polls/base.html' %}
{% block content %}
<form class="form-inline" action="{% url 'two_factor:confirm' %}" method="post">
{% csrf_token %}
<input type="hidden" name="next" value="{{request.GET.next}}">
<p>
We have sent a code to your number. Please type it in below.
</p>
<div class="form-group">
<input type="text" name="code" class="form-control">
</div>
<div class="form-group">
<input type="submit" name="name" value="Confirm" class="btn btn-primary">
</div>
</form>
{% endblock %} Verify le code de l'utilisateur
Screengrab of 2 Factor Authentication Form
La dernière étape de ce tutoriel consiste à confirmer le code fourni par l'utilisateur. Commençons par ajouter la route pour cette page.
# two_factor/urls.py
urlpatterns = [
...
url(r'^confirm/$', views.ConfirmView.as_view(), name='confirm'),
]Dans l'étape précédente, l'utilisateur s'est vu présenter un formulaire avec un champ code champ. Lorsqu'il soumet ce formulaire à l two_factor:verify nous devrons appeler la bibliothèque vonage avec le code et les request_id que nous avons stockés dans la session plus tôt.
# two_factor/views.py
class ConfirmView(LoginRequiredMixin, View):
def post(self, request):
response = self.check_verification_request(request)
if (response['status'] == '0'):
request.session['verified'] = True
return HttpResponseRedirect(request.POST['next'])
else:
messages.add_message(request, messages.INFO, 'Could not verify code. Please try again.')
return HttpResponseRedirect(reverse('two_factor:verify')+"?next="+request.POST['next'])
def check_verification_request(self, request):
return nexmo.Client().check_verification(request.session['verification_id'], code=request.POST['code'])Nous utilisons la fonction nexmo.Client().check_verification pour vérifier que le code est valide pour le request_id. En cas de succès, le code d'état sera 0 et nous marquons la session comme vérifiée. Lorsque nous redirigeons l'utilisateur vers la page sur laquelle il a commencé, la fonction TwoFactorMixin ne redirigera plus l'utilisateur, mais lui permettra de consulter le sondage.
Using Vonage for 2 Authentication Factor
Prochaines étapes
Il y a beaucoup plus d'options dans l Verify API de Vonage de Vonage que nous avons abordées ici. Le code que nous avons montré ici est assez simple et il existe de nombreuses façons différentes de mettre en œuvre cette expérience utilisateur. Le système Vonage Verify est extrêmement résistant, puisqu'il revient aux appels téléphoniques si nécessaire, expire les jetons sans que vous ayez à faire quoi que ce soit, empêche la réutilisation des jetons et enregistre les temps de vérification.
La bibliothèque Python de Vonage est très agnostique quant à son utilisation, ce qui signifie que vous pourriez mettre en œuvre des choses très différentes de ce que j'ai fait ici. J'aimerais savoir ce que vous ajouteriez ensuite ? N'hésitez pas à m'envoyer un tweet (je suis @cbetta) pour me faire part de vos réflexions et de vos idées.