title: Adding two factor authentication in Django/Django Rest
body: <p>I know this topic has been widely discussed, but most of the examples are about two factor authentication in standard Django templates, while in my case i want to add two factor authentication to a project where Django is used as an API on the backend while the frontend is a native VueJS application.</p>
  <p>For eveything authentication related, i'm using the built-in Django session authentication, since both frontend and backend are deployed on the same server.</p>
  <p><strong>My question is</strong>: how can i add two factor authentication (using google authenticator or yubikey) to a project where django is used as an API?</p>
  <p><strong>Here is the problem</strong>: the easiest way to do this would be to let the user login from the frontend, and once the user is logged in from <code>/accounts/login</code> (built-in django authentication view), submit a form where the user has to input their code. The problem with this approach is that once the user is logged in Django will create a session, so <code>request.user.is_authenticated</code> will return <code>True</code> even though the user didn't submit the Two Factor code yet, so everything would depend on the frontend. I don't like this approach because i'm afraid that someone might find a way to avoid submitting the two factor form and navigate on the rest of the site (since according to Django that session would be authenticated) without the two factor authentication</p>
  <p><strong>What i tried:</strong> I still have to write most of the code for this, because i want to understand how safe is it first. But here is my approach:</p>
  <p><strong>First approach</strong></p>
  <ol>
  <li>User submits the login form</li>
  <li>Once the login form is submitted, a POST request with the credentials is sent to an endpoint called <code>/authenticate</code> in my Django app. This endpoint will use the Django built-in <code>authenticate()</code> method that will check if those credentials belong to a user without creating a session.</li>
  <li>If the credentials belong to a user, it will return <code>True</code> to the user. At this point the user will submit a form with the 2FA code, and if the code is right, the request is sent to <code>/accounts/login</code> which will check again password and email and actually login the user and create the session, this time.</li>
  </ol>
  <p><strong>Second approach</strong>
  Another approach, that would be even better, would be to override the Django-Allauth login view so that i can add a check for the token, so something like (WARNING: pseudo-code):</p>
  <pre><code>if provided_code == user_code:
     login()
     return HttpResponse({'Result': 'Logged in!'})
  else:
     return HttpResponse({'Result': 'incorrect code'})
  </code></pre>
  <p>Where <code>provided_code</code> is the 2FA code provided by the user and <code>user_code</code> is the correct code that i will retrieve from another function like <code>get_user_2fa_code(user)</code>.</p>
  <p>I'm not a security expert, but i didn't come up with better approaches. How safe would it be? Is there a better way to add 2FA auth to a Django project where django works as a backend API?</p>
  <p><a href="https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py" rel="noreferrer">Here is the login view</a>:</p>
  <pre><code>class LoginView(
      RedirectAuthenticatedUserMixin, AjaxCapableProcessFormViewMixin, FormView
  ):
      form_class = LoginForm
      template_name = &quot;account/login.&quot; + app_settings.TEMPLATE_EXTENSION
      success_url = None
      redirect_field_name = &quot;next&quot;
  
      @sensitive_post_parameters_m
      def dispatch(self, request, *args, **kwargs):
          return super(LoginView, self).dispatch(request, *args, **kwargs)
  
      def get_form_kwargs(self):
          kwargs = super(LoginView, self).get_form_kwargs()
          kwargs[&quot;request&quot;] = self.request
          return kwargs
  
      def get_form_class(self):
          return get_form_class(app_settings.FORMS, &quot;login&quot;, self.form_class)
  
      def form_valid(self, form):
          success_url = self.get_success_url()
          try:
              return form.login(self.request, redirect_url=success_url)
          except ImmediateHttpResponse as e:
              return e.response
  
      def get_success_url(self):
          # Explicitly passed ?next= URL takes precedence
          ret = (
              get_next_redirect_url(self.request, self.redirect_field_name)
              or self.success_url
          )
          return ret
  
      def get_context_data(self, **kwargs):
          ret = super(LoginView, self).get_context_data(**kwargs)
          signup_url = passthrough_next_redirect_url(
              self.request, reverse(&quot;account_signup&quot;), self.redirect_field_name
          )
          redirect_field_value = get_request_param(self.request, self.redirect_field_name)
          site = get_current_site(self.request)
  
          ret.update(
              {
                  &quot;signup_url&quot;: signup_url,
                  &quot;site&quot;: site,
                  &quot;redirect_field_name&quot;: self.redirect_field_name,
                  &quot;redirect_field_value&quot;: redirect_field_value,
              }
          )
          return ret
  </code></pre>