
Adicione a autenticação de dois fatores ao seu aplicativo Django com o Nexmo
Tempo de leitura: 12 minutos
Já havia demonstrado meu apreço pela autenticação de dois fatores no blog da Vonage, com um aplicativo de demonstração para minha empresa “Kittens & Co”. Curiosamente, nem todo mundo é fã de gatos; alguns de nós preferem cães, outros preferem outros animais, mas todos nós adoramos a autenticação de dois fatores, certo?
Vamos fazer uma pequena enquete
Neste tutorial, vou mostrar a vocês como adicionar a autenticação de dois fatores ao seu site Django usando a Verify API do Vonage Verify. Para isso, criei um pequeno aplicativo chamado “Pollstr” — um aplicativo web simples para fazer enquetes. Sei que vai ser um sucesso instantâneo por causa do “e” que falta no nome. Quero adicionar a autenticação de dois fatores para garantir que as pessoas sejam realmente quem dizem ser e para evitar spam nas minhas enquetes.
Pollstr screenshot
Você pode baixar o código inicial do aplicativo em GitHub e executá-lo localmente.
Em seguida, acesse 127.0.0.1:8000 no seu navegador e tente votar em uma enquete. Você pode fazer login com estas credenciais:
nome de usuário:
testsenha:
test1234
Por padrão, o aplicativo implementa o cadastro e o login usando a estrutura de autenticação integrada do Django, mas a maior parte deste tutorial se aplica de maneira semelhante a aplicativos que utilizam outros métodos de autenticação. Além disso, adicionamos algumas configurações iniciais para melhorar a aparência do nosso aplicativo.
Todo o código para este ponto de partida pode ser encontrado no ramo no GitHub. Todo o código que adicionaremos a seguir pode ser encontrado no after . Para sua conveniência, você pode ver todas as alterações entre nosso ponto inicial e final no GitHub também.
Vonage Verify para autenticação de duas etapas (2FA)
Vonage Verify é uma maneira simples e segura de implementar a verificação por telefone com apenas duas chamadas de API! Na maioria dos sistemas de autenticação de dois fatores, você precisa gerenciar seus próprios tokens, a validade deles, as tentativas de reposição e o envio de SMS. O Vonage Verify cuida de tudo isso para você.
Para adicionar o Vonage Verify ao nosso aplicativo, faremos as seguintes alterações:
Adicione um
phone_numberao nosso usuárioAdicione um
TwoFactorMixinem nossas visualizações para garantir que o usuário esteja conectado e tenha sido VerifiedRegistrar um novo número de telefone para novos usuários
Envie um código de verificação ao usuário
Verify the code sent to the number
Adicionar um número de telefone
O modelo de usuário padrão do Django não possui um número de telefone, então teremos que adicionar um nós mesmos. Existem algumas maneiras de fazer isso, mas, neste caso, vamos manter todo o nosso novo código restrito a um novo two_factor aplicativo.
Isso vai gerar muitos arquivos novos na /two_factor pasta. Vamos abrir o /two_factor/models.py e vamos adicionar um novo modelo que tenha uma relação um-para-um com nosso usuário.
# 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)Em seguida, vamos gerar as migrações para esse modelo, mas, para isso, primeiro precisamos nos certificar de adicionar two_factor.apps.TwoFactorConfig ao nosso INSTALLED_APPS.
# pollstr/settings.py
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'two_factor.apps.TwoFactorConfig',
'django.contrib.admin',
...
]Com isso pronto, podemos gerar nossas migrações e migrar nosso banco de dados:
Adicionando um TwoFactorMixin
Nosso aplicativo Django utiliza visões baseadas em classes , o que nos permite usar “mixins” personalizados para adicionar nosso próprio comportamento a cada visualização. Atualmente, usamos o LoginRequiredMixin para garantir que estejamos conectados antes de podermos votar nas enquetes.
# polls/views.yml
class OptionsView(LoginRequiredMixin, DetailView):
...Vamos implementar um novo TwoFactorMixin para adicionar uma camada TwoFactor a essa verificação. Vamos começar alterando nossas visualizações para usar esse novo mixin, mesmo que ainda não o tenhamos escrito.
# polls/views.py
from two_factor.mixins import TwoFactorMixin
class OptionsView(TwoFactorMixin, DetailView):
...
class ResultsView(TwoFactorMixin, DetailView):
...
class VoteView(TwoFactorMixin, View):
...Agora vamos adicionar o mixin ao nosso two_factor aplicativo:
# 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')O que fizemos aqui foi criar um novo mixin que, por sua vez, utiliza o UserPassesTestMixin. Esse mixin, então, chama automaticamente a test_func função na qual verificamos se o usuário está conectado e se essa sessão foi validada. Fazemos isso simplesmente verificando se a chave verified foi definida na sessão. Ao usar a sessão dessa forma, é possível que alguém esteja conectado em vários computadores, mas ainda assim seja necessária a verificação em cada um deles.
A get_login função fornece UserPassesTestMixin uma rota para a qual redirecionar o usuário caso o teste falhe. Nesse caso, temos dois cenários: um em que o usuário não está conectado e outro em que está conectado, mas não foi Verifyado.
Se você executasse seu servidor neste momento, ele falharia porque, bem, ainda não implementamos nenhuma das rotas ou visualizações para as quais redirecionar o usuário. Vamos fazer isso a seguir.
Selecionando um número de telefone
Screen Capture of Number Verification Form
Quando o usuário precisa ser verificado, ele é redirecionado para two_factor:new onde pediremos que ele defina ou confirme o número de telefone para o qual enviaremos um código.
# 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'),
]Também adicionamos os URLs para nossas próximas etapas. Agora, precisamos nos certificar de importar esses URLs para nosso aplicativo principal.
# pollstr/urls.py
urlpatterns = [
...
url(r'^polls/', include('polls.urls')),
url(r'^2fa/', include('two_factor.urls')),
]Quando o aplicativo redirecionar para /2fa/ ele tentará renderizar a NewView visualização. Essa visualização disponibilizará o TwoFactor modelo disponível para o modelo de visualização, mas precisamos interceptar a exceção óbvia quando o usuário ainda não tiver um TwoFactor objeto ainda e, em vez disso, inicializar um.
# 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)Tentamos retornar o user.twofactor registro, mas, se ele não existir, inicializamos um e retornamos esse.
A visualização exibe o two_factor/new.html modelo que permitirá ao usuário inserir seu número de telefone ou exibirá o número já fornecido em um campo desativado. Ignoraremos o número no campo desativado mais tarde, caso ele já tenha sido definido, mas isso serve como um bom lembrete para o usuário sobre para qual número o código será enviado.
<!-- 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 %}
Ignorando a sobrecarga do Bootstrap, nosso formulário é um formulário básico com alguns campos:
O
numberpara enviar um código paraA
nextpágina para a qual redirecionar depois que terminarmos a verificação; esse é um recurso integrado do Django, então vamos aproveitá-lo bem.
Quando o formulário for enviado para /2fa/create precisaremos enviar o código ao usuário por meio da Vonage.
Como usar o Vonage Verify
Vonage Verify é muito fácil de usar e, basicamente, resume-se a duas chamadas de API. A primeira envia o código de verificação para o número de telefone do usuário. No nosso caso, isso ocorrerá CreateView quando o formulário for enviado.
Para enviar o código, precisaremos da vonage biblioteca Python. Já a adicionamos ao seu requirements.txt juntamente com a django-dotenv biblioteca que nos permitirá carregar nossas credenciais a partir de um .env arquivo. Se você preferir uma maneira diferente de gerenciar as dependências do seu aplicativo, pode instalá-las diretamente com o pip.
A vonage biblioteca pode ser instanciada com uma chave e um segredo da API ou definindo algumas variáveis de ambiente. Você pode obter sua chave e o segredo da API da Vonage no painel.
Com essas variáveis de ambiente definidas, agora não precisamos mais inicializar nosso cliente Vonage e podemos usá-lo diretamente da seguinte maneira.
# 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')O código aqui realiza algumas ações. Primeiro, ele usa find_or_set_number para verificar se o usuário já tem um número de telefone definido e somente se ele não estiver definido ele salvará o número que o usuário inseriu.
Em seguida, ele utiliza nexmo.Client().start_verification para iniciar o processo de verificação. Passamos dois parâmetros aqui: o number do usuário e um brand que aparecerá na mensagem de texto que enviaremos.
Em seguida, verificamos se a status nossa chamada de API é 0 e, se for, armazenamos o request_id dessa tentativa de verificação na sessão. Fazemos isso porque precisaremos desse mesmo id mais tarde para confirmar o código que o usuário recebeu.
Por fim, redirecionamos o usuário para nossa VerifyView que é uma visualização simples que exibe um formulário para inserir o código de verificação.
# two_factor/views.py
from django.views.generic import DetailView, View, TemplateView
class VerifyView(LoginRequiredMixin, TemplateView):
template_name = 'two_factor/verify.html'E o modelo correspondente. Como você pode ver, ainda estamos passando o next valor, para que possamos redirecionar de volta para a enquete correta no final.
<!-- 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 %} Verificação do código do usuário
Screengrab of 2 Factor Authentication Form
A última etapa deste tutorial é confirmar o código fornecido pelo usuário. Vamos primeiro adicionar a rota para essa página.
# two_factor/urls.py
urlpatterns = [
...
url(r'^confirm/$', views.ConfirmView.as_view(), name='confirm'),
]Na etapa anterior, foi exibido ao usuário um formulário com um code campo. Ao enviá-lo para a two_factor:verify URL, precisaremos chamar a vonage biblioteca novamente com o código e o request_id que armazenamos na sessão anteriormente.
# 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'])Usamos a nexmo.Client().check_verification função para verificar se o código é válido para o request_id. Se a verificação for bem-sucedida, o código de status será 0 e marcamos a sessão como verificada. Quando redirecionamos o usuário para a página em que ele começou, o TwoFactorMixin não redirecionará mais o usuário, mas permitirá que ele visualize a enquete.
Using Vonage for 2 Authentication Factor
Próximos passos
Existem muitas outras opções na Verify API do Vonage Verify do que as que abordamos aqui. O código que mostramos aqui é bem simples, e há muitas maneiras diferentes de implementar essa experiência do usuário. O sistema Vonage Verify é extremamente resiliente, pois recorre a chamadas telefônicas se necessário, expira os tokens sem que você precise fazer nada, impede a reutilização de tokens e registra os horários das verificações.
A biblioteca Python da Vonage é bastante flexível quanto à forma como é utilizada, o que significa que você poderia implementar as coisas de maneira bem diferente da que fiz aqui. Adoraria saber o que você acrescentaria a seguir. Por favor, me mande um tweet (meu nome de usuário é @cbetta) com suas opiniões e ideias.