how to add otp authentication in django login page

Viewed 4211

Firstly, the login.html should ask for 'Enter OTP' once the email(username) is entered and submitted. This username is checked with user table if the user exists. If user exists , it should send OTP to the mobile registered for this user instance. On entry of the OTP, user should get appropriate message to reset password or get the home page. I don't want to use the django-otp app. What i have done so far: In the django accounts/registration/templates/login.html

{% extends "admin/base_site.html" %}
{% load i18n static %}

{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/login.css" %}">
{{ form.media }}
{% endblock %}

{% block bodyclass %}{{ block.super }} login{% endblock %}

{% block usertools %}{% endblock %}

{% block nav-global %}{% endblock %}

{% block content_title %}{% endblock %}

{% block breadcrumbs %}{% endblock %}

{% block content %}
{% if form.errors and not form.non_field_errors %}
<p class="errornote">
{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
</p>
{% endif %}

{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<p class="errornote">
    {{ error }}
</p>
{% endfor %}
{% endif %}

<div id="content-main">

{% if user.is_authenticated %}
<p class="errornote">
{% blocktrans trimmed %}
    You are authenticated as {{ username }}, but are not authorized to
    access this page. Would you like to login to a different account?
{% endblocktrans %}
</p>
{% endif %}

<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
  <div class="form-row">
    {{ form.username.errors }}
    {{ 'Email:' }} {{ form.username }}
  </div>
  <div class="form-row">
    {{ form.password.errors }}
    <!-- {{ form.password.label_tag }} {{ form.password }} -->
    <input type="hidden" name="next" value="{{ next }}">
  </div>
  {% url 'admin_password_reset' as password_reset_url %}
  {% if password_reset_url %}
  {% comment %}
  <div class="password-reset-link">
    <a href="{{ password_reset_url }}">{% trans 'Forgotten your password or username?' %}</a>
  </div>
  {% endcomment %}

  {% endif %}
  <div>
    {% trans 'Enter OTP' %}</a>
    <input type="integer" name="otp" value="{{ otp }}">
  </div>


  <div class="submit-row">
    <label>&nbsp;</label><input type="submit" value="{% trans 'Log in' %}">
  </div>
</form>

</div>
{% endblock %}

AuthenticationForm:

from django import forms
from django.db.models import IntegerField
from django.contrib.auth.forms import AuthenticationForm, UsernameField

class AuthenticationForm(AuthenticationForm):
    class Meta:
        model = User
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super(AuthenticationForm, self).__init__(*args, **kwargs)

        for field in self.fields.values():
            field.error_messages = {'required':'{fieldname} is required'.format(
            fieldname=field.label)}
            
        username = UsernameField(
            label='Email',
            widget=forms.TextInput(attrs={'autofocus': True})
        )

        otp = IntegerField()

myauthentication backend code:

from django.contrib.auth.backends import ModelBackend, UserModel
from django.db.models import Q
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.contrib.auth.models import User, Group
from myapp.models import *
from django.http import *
from datetime import datetime
from django.urls import reverse
from django.utils.translation import ugettext as _
from myapp.forms import AuthenticationForm

import pyotp
from rest_framework.response import Response
from rest_framework.views import APIView
import base64

def generateKey(phone):
    return str(phone) + str(datetime.date(datetime.now())) + "Some Random Secret Key"

class EmailBackend(ModelBackend):
    #@staticmethod
    def authenticate(self, request, username=None, password=None, **kwargs):
        mMobile = None
        user = None
        form = AuthenticationForm(request=request, data=request.GET)
        if request.GET:
            try:
                #to allow authentication through phone number or any other
                #field, modify the below statement
                user = UserModel.objects.get(
                    Q(username__iexact=username) | Q(email__iexact=username))
            except UserModel.DoesNotExist:
                print('iiii')
                UserModel().set_password(password)
            except MultipleObjectsReturned:
                print(222222)
                user = User.objects.filter(email=username).order_by('id').first()
            else:
                if user.check_password(password) and self.user_can_authenticate(
                    user):
        form = AuthenticationForm(request=request, data=request.POST)
        if form['username']:
            try:
                mMobile = Mailbox.objects.get(email=form['username'].value())
                #print(user,mMobile, 'uuu-mmmm', dir(mMobile))
            except Exception as e:
                print(e)
            #return user
        
            if mMobile:
                mMobile.counter += 1  # Update Counter At every Call
                mMobile.save()  # SamMove the data
                print(mMobile.mobile)
                keygen = generateKey(mMobile.mobile)
                # Key is generated
                key = base64.b32encode(keygen.encode())
                OTP = pyotp.HOTP(key)
                motp = (OTP.at(mMobile.counter))
                print(motp, 'oooottttpp')

                if request.POST:
                    if str(motp) == form.data['otp']:
                        print(form.data['otp'],'ddddd')
                        return user
                    else:
                        return
                return
                # Using Multi-Threading send the OTP Using Messaging
                # Services like Twilio or Fast2sms                
                #return user
            else:
                return user

    def get_user(self, user_id):
        try:
            user = UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None

        return user if self.user_can_authenticate(user) else None

Problem: on entering username and submitting, it should send otp sms. Right now it sends the otp when the form is submitted. How exactly to incorporate this wherein the login form can be resubmitted 2 times? Using django 3.1, postgresql database. I need the exact code Some parts like otp generation I have used from here: [https://github.com/Akash16s/OTP-in-django][1]

[1]: https://github.com/Akash16s/OTP-in-django Feel stuck. How to go about?

1 Answers

This is how I did using my models.py

Below is the model.py

class Customer(models.Model):
    user = models.OneToOneField(User, null=True, blank =True, on_delete=models.CASCADE)
    date_created = models.DateTimeField(auto_now_add=True, null=True)
    name = models.CharField(max_length=200, null=True)
    first_name = models.CharField(max_length=200, null=True)
    last_name = models.CharField(max_length=200, null=True)
    email = models.CharField(max_length=200, unique=True)
    phone = models.CharField(max_length=200, null=True)
    platformLogo= models.ImageField(upload_to=upload_path, default='logo.png', null=True, blank=False)

Here is my forms.py

from django.forms import ModelForm
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from .models import Customer
from django import forms


class CustomerForm(ModelForm):
    class Meta:
        model = Customer
        fields  = '__all__'
        exclude = ['user', 'email','name','otp_code']


class CreateUserForm(UserCreationForm):
    class Meta:
        model = User
        fields  = ['username','first_name','last_name', 'email', 'password1', 'password2']

Here is my views.py

def loginUser(request): 
    if request.method == "POST":
        username = request.POST.get('username')
        password = request.POST.get('password')
        try:
            user = authenticate(username=username, password=password)
            if user is not None:
                user = User.objects.get(username=username)
                otp= random.randrange(100000,999999)
                user.customer.otp_code = otp
                user.customer.save()
                request.session['username'] = username
                body = f"Dear {username}, your OTP for login is {otp}. Use this OTP to validate your login."
                send_mail('OTP request',body,'email@gmail.com',[username], fail_silently=False)
                messages.success(request, "Your OTP has been send to your email.")
                return redirect("/otp_verification")
            else:
                messages.error(request, "Wrong Credentials!!")
                return render(request,'login.html')
        except:
            messages.error(request, "Please enter email and password for login!")
            return render(request,'login.html')
    context={}    
    return render(request, "login.html", context)



def otp_verification(request):
    username = request.session['username']
    if request.method == "POST":
        otp = request.POST.get('username')
        user = User.objects.filter(username = username).first()
        if otp == user.customer.otp_code:
            messages.success(request, "OTP Success. Please login with your credentials!")
            login(request, user)
            messages.success(request, f' Wecome {username}')
            return redirect("/")
        else:
            messages.error(request, "Wrong OTP!!")
    return render(request, "otpVerification.html")
Related