Django auth with otp and phone number without password

I want user login without password, just by sending an SMS to the user's phone number and entering the code. I used sessions to transfer user inputs and I just don't know if this is correct or if there is a better way for this? Is my model implementaion is correct ? This is my code:

# models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager


class MyAccountManager(BaseUserManager):

    def create_superuser(self,username, email, phone_number,password, **other_fields):
        
        other_fields.setdefault('is_staff', True)
        other_fields.setdefault('is_superuser', True)
        other_fields.setdefault('is_active', True)
      
        if other_fields.get('is_staff') is not True:
            raise ValueError('Superuser must be assigned to is_staff=True')
       
        if other_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must be assigned to is_superuser=True')

        user =  self.create_user(username,email, phone_number, password, **other_fields)
        user.set_password(password)
        user.save()
        return user

    def create_user(self, username, email, phone_number,password,**other_fields):
        if not email:
            raise ValueError('')
        
        email = self.normalize_email(email)
        
        if password is not None:
            user = self.model(username=username,email=email, phone_number=phone_number,password=password, **other_fields)
            user.save()
        else:
            user = self.model(username=username,email=email, phone_number=phone_number, password=password,**other_fields)
            user.set_unusable_password()
            user.save()

        return user


class Account(AbstractBaseUser, PermissionsMixin):
   
    email = models.EmailField(unique=True)
    username = models.CharField(max_length=150,unique=True)
    phone_number = models.CharField(max_length=17, unique=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
   
    objects = MyAccountManager()
    USERNAME_FIELD = 'phone_number'
    REQUIRED_FIELDS = ['email', 'username']


# views.py
from django.shortcuts import render, redirect, HttpResponse
from django.contrib.auth import authenticate, login, get_user_model
from .forms import RegisterForm, LoginForm, OtpConfirmForm
from django.contrib.auth.backends import ModelBackend

User = get_user_model()

class PasswordlessAuthBackend(ModelBackend):
    # Log in to Django without providing a password.    
    def authenticate(self, request, phone_number):
        User = get_user_model()
        try:
            user = User.objects.get(phone_number=phone_number)
            return user
        except User.DoesNotExist:
            return None

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


def login_view(request):
    form = LoginForm(request.POST or None)
    context = {'form': form}

    if form.is_valid():
        request.session['phone_number'] = form.cleaned_data.get('phone_number')

        request.session['otp'] = 12345
        # sendin otp functionality
        return redirect('login-confirm')
    
    return render(request, 'login.html', context)


def login_confirm_view(request):
    form = OtpConfirmForm(request.POST or None)
    context = {'form': form}

    if form.is_valid():
        phone_number = request.session['phone_number']
        form_otp = form.cleaned_data.get('otp')
        session_otp = request.session['otp']
        if form_otp == session_otp:
            user = authenticate(request, phone_number=phone_number)

            if user is not None:
                login(request, user, backend='account.views.PasswordlessAuthBackend')
                return redirect('home')
            else: return HttpResponse('invalid login')
    
    return render(request, 'login-confirm.html', context)

Thank you for helping me.

Back to Top