0

I have an OTP generator mechanism, whereby in GET method, a unique 4-dit OTP is generated and is sent to the OTPAuthenticationForm() as follows:

views.py

if request.method == 'GET':
   otp = 'abcd'
   # code to send otp via mail
   form = OTPAuthenticationForm(request=request, otp=otp) # this is how the OTP is passed only once.
   return ....
elif request.method == 'POST':
    form = OTPAuthenticationForm(request.POST, request=request)
    if form.is_valid():                 # the clean_field() method is not invoked/throws error.
       print("OTP is valid")
    else: 
       print("OTP is invalid")

forms.py

from django.contrib.auth.forms import AuthenticationForm


class OTPAuthenticationForm(AuthenticationForm):
    otp = forms.CharField(required=True, widget=forms.TextInput)

    def __init__(self, otp, *args, **kwargs):
       self.otp = kwargs.pop("otp")

       super(OTPAuthenticationForm, self).__init__(*args, **kwargs)

    def clean(self):
       if self.otp!= self.cleaned_data['otp']:
          raise forms.ValidationError("Invalid OTP")

How should the generated_otp be passed only once to the OTPAuthForm() so that it could be validated against the user input OTP (OTP received via email).

The clean() method is not getting invoked; instead on execution of form.is_valid(), the form return 'invalid' error

Update: The OTPAuthenticationForm(forms.Form) was earlier importing the class AuthenticationForm() which requires the username, password fields along with the custom OTP field.

Which is why the form.is_valid() was returning as invalid since the username, password auth was being separately handled in the application.

1 Answer 1

0

I think you should not pass otp in GET request, instead it should by done by POST request:

form = OTPAuthenticationForm(request.POST, request=request, otp=otp)

And update the Form as well:

class OTPAuthenticationForm(AuthenticationForm):
    otp = forms.CharField(required=True, widget=forms.TextInput)

    def __init__(self, otp, *args, **kwargs):
       self.otp_value = kwargs.pop("otp", None)
       super(OTPAuthenticationForm, self).__init__(*args, **kwargs)

    def clean(self):
       otp = self.cleaned_data['otp']
       if self.otp_value != otp:
          raise forms.ValidationError("Invalid OTP")
       return super(OTPAuthenticationForm, self).clean()

Now, the question is how to store that OTP when you get the POST request. You can use session. Like this:

if request.method == 'GET':
   otp = 'abcd'
   form = OTPAuthenticationForm(request=request)
   request.session['otp'] = otp  # storing data in session
   return ....
elif request.method == 'POST':
    form = OTPAuthenticationForm(request.POST, request=request, otp=request.session.get('otp'))
    if form.is_valid():
        del request.session['otp'] # deleting data in session
        ...
Sign up to request clarification or add additional context in comments.

3 Comments

your solution is good, however the clean() method is not getting invoked. I am still encountering invalid form error.
the variable is getting stored and retrieved in the session, but the clean() method is not getting invoked(found after debugging)
the OTPAuthenticationForm() must import forms.Form instead of the class AuthenticationForm if only the OTP fields needs to be validated.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.