https://a.storyblok.com/f/270183/79625/0c1ed09de4/python-django-2fa.png

Adicione a autenticação de dois fatores ao seu aplicativo Django com o Nexmo

Publicado em July 13, 2017

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 screenshotPollstr screenshot

Você pode baixar o código inicial do aplicativo em GitHub e executá-lo localmente.

# ensure you have Python and pip installed git clone https://github.com/nexmo-community/django-2fa-demo.git cd nexmo-django-2fa-demo pip install -r requirements.txt python manage.py migrate python manage.py loaddata fixtures/all.json python manage.py runserver

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: test

  • senha: 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_number ao nosso usuário

  • Adicione um TwoFactorMixin em nossas visualizações para garantir que o usuário esteja conectado e tenha sido Verified

  • Registrar 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.

python manage.py startapp two_factor

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:

python manage.py makemigrations two_factor Migrations for 'two_factor': 0001_initial.py: - Create model TwoFactor python manage.py migrate Operations to perform: Apply all migrations: sessions, admin, two_factor, polls, auth, contenttypes Running migrations: Rendering model states... DONE ...

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 FormScreen 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 number para enviar um código para

  • A next pá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.

pip install nexmo pip install django-dotenv

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.

# .env NEXMO_API_KEY=123 NEXMO_API_SECRET=234

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 FormScreengrab 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 FactorUsing 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.

Compartilhar:

https://a.storyblok.com/f/270183/169x169/d811e67494/cristiano-betta.png
Cristiano BettaEx-funcionários da Vonage