'django-otp implementation using custom Views
I have been trying to implement the django-otp with qrcode using custom Forms and Views. The problem is I am a little caught up on whether my implementation is correct or not. As the documentation states that a request.user.is_verified() attribute is added to users who have been OTP verified, I am actually not able to get it right. Have created confirmed TOTP device for user with the QR code setup using Microsoft Authenticator app.
I was able to successfully implement the default Admin Site OTP verification without any issues. Below is the files for the custom implementation.
urls.py
from django.conf.urls import url
from account.views import AccountLoginView, AccountHomeView, AccountLogoutView
urlpatterns = [
url(r'^login/$', AccountLoginView.as_view(), name='account-login',),
url(r'^home/$', AccountHomeView.as_view(), name='account-home',),
url(r'^logout/$', AccountLogoutView.as_view(), name='account-logout',)
]
views.py
from django.contrib.auth import authenticate, login as auth_login
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView
from django_otp.forms import OTPAuthenticationForm
class AccountLoginView(FormView):
template_name = 'login.html'
form_class = OTPAuthenticationForm
success_url = '/account/home/'
def form_invalid(self, form):
return super().form_invalid(form)
def form_valid(self, form):
# self.request.user returns AnonymousUser
# self.request.user.is_authenticated returns False
# self.request.user.is_verified() returns False
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
otp_token = form.cleaned_data.get('otp_token')
otp_device = form.cleaned_data.get('otp_device')
user = authenticate(request=self.request, username=username, password=password)
if user is not None:
device_match = match_token(user=user, token=otp_token)
# device_match returns None
auth_login(self.request, user)
# self.request.user returns [email protected]
# self.request.user.authenticated returns True
# self.request.user.is_verified returns AttributeError 'User' object has no attribute 'is_verified'
# user.is_verified returns AttributeError 'User' object has no attribute 'is_verified'
return super().form_valid(form)
class AccountHomeView(TemplateView):
template_name = 'account.html'
def get(self, request, *args, **kwargs):
# request.user.is_authenticated returns True
# request.user.is_verified() returns False
return super(AccountHomeView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['data'] = 'This is secured text'
return context
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="." method="post">
{% csrf_token %}
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.username.errors }}
<label for="{{ form.username.id_for_label }}">{{ form.username.label_tag }}</label>
{{ form.username }}
</div>
<div class="fieldWrapper">
{{ form.password.errors }}
<label for="{{ form.password.id_for_label }}">{{ form.password.label_tag }}</label>
{{ form.password }}
</div>
{% if form.get_user %}
<div class="fieldWrapper">
{{ form.otp_device.errors }}
<label for="{{ form.otp_device.id_for_label }}">{{ form.otp_device.label_tag }}</label>
{{ form.otp_device }}
</div>
{% endif %}
<div class="fieldWrapper">
{{ form.otp_token.errors }}
<label for="{{ form.otp_token.id_for_label }}">{{ form.otp_token.label_tag }}</label>
{{ form.otp_token }}
</div>
<input type="submit" value="Log In" />
{% if form.get_user %}
<input type="submit" name="otp_challenge" value="Get Challenge" />
{% endif %}
</form>
</body>
</html>
Could anyone please let me know what is that I am missing. I want to be able to authenticate and verify them using my own views by using the existing OTP form classes.
Please advice.
Solution 1:[1]
What exactly is match_token? You don't need a device field, rather you try and a device for a user.
Here's my implementation for that.
from django_otp import devices_for_user
from django_otp.plugins.otp_totp.models import TOTPDevice
def get_user_totp_device(user, confirmed=None):
devices = devices_for_user(user, confirmed=confirmed)
for device in devices:
if isinstance(device, TOTPDevice):
return device
def create_device_topt_for_user(user):
device = get_user_totp_device(user)
if not device:
device = user.totpdevice_set.create(confirmed=False)
return device.config_url
def validate_user_otp(user, data):
device = get_user_totp_device(user)
serializer = otp_serializers.TokenSerializer(data=data)
if not serializer.is_valid():
return dict(data='Invalid data', status=status.HTTP_400_BAD_REQUEST)
elif device is None:
return dict(data='No device registered.', status=status.HTTP_400_BAD_REQUEST)
elif device.verify_token(serializer.data.get('token')):
if not device.confirmed:
device.confirmed = True
device.save()
return dict(data='Successfully confirmed and saved device..', status=status.HTTP_201_CREATED)
else:
return dict(data="OTP code has been verified.", status=status.HTTP_200_OK)
else:
return dict(
data=
dict(
statusText='The code you entered is invalid',
status=status.HTTP_400_BAD_REQUEST
),
status=status.HTTP_400_BAD_REQUEST
)
And then in a view, you can just do something like
create_device_topt_for_user(user=request.user)
validate_user_otp(request.user, request.data)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Simeon Aleksov |
