Customize Django Auth Login Functionality

I am currently using the default Django auth system for logging in the user, I want to modify it so that instead of the default auth url executing it’s own login function, I want to add some additional steps to the login system.

Basically I want to generate an otp, a randomized 6 digit number and mail it to the email of the user who attempted to login, provided the users entered details were authenticated and verified using the default authenticate method.

After the user clicks the login button, if their entered data is authenticated, the email will be sent with an otp, and the user will be shown a page to enter the otp and verify it.

Once the otp is verified, the user is redirected to another page which shows the user some messages, what these messages are is not important, what is important is that user must be logged in AFTER the messages have been displayed, there must also be a conditional statement which returns the user to the main page if for whatever reason the messages were not shown. The messages will be coming from an API, and depending on the message the user will either be logged in or returned to the main home page.

Only then should the user be logged in completely, and be allowed to access urls decorated with the logged in decorator.

Thanks for the help in advance.

You can create a custom login function. here I’m sharing a code snippet for your reference

from django.contrib.auth.models import User
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login
from django.contrib import messages

def login_view(request):
    if request.method == "POST":
        username = request.POST.get("username").lower()
        password = request.POST.get("password")

        try:
            user = User.objects.get(username=username)
        except:
            messages.error(request, "User Not Found....")
            return redirect("home")

        if user is not None:
            login(request, user)
            return redirect("home")
        else:
            messages.error(request, "Username or Password does not match...")

    return render(request, "login.html")

So if I change the url to use this view, then I can login with the default auth login function in addition to my custom logic?

No you are not using default auth login function anymore if you use the custom login view.

Ok, but the login(request, user) will do the same thing as logging in normally?

Yes, it will store your user to session.

Actually I have another question, how can I use the original user log in form, as it seems to have disappeared now, I am using crisper forms.

original user log in form for this you can inherit it like this and use it to pass from the view, also you can create a complete custom form for this as well.

from django import forms
from django.contrib.auth.forms import AuthenticationForm, UsernameField

class LoginForm(AuthenticationForm):
    username = UsernameField(label='Enter Username', widget=forms.TextInput(attrs={'class':'form-control'}))
    password = forms.CharField(label='Enter Password', widget=forms.PasswordInput(attrs={'class':'form-control'}))
    
    def confirm_login_allowed(self, user):
        if user.is_staff and not user.is_superuser:
            raise ValidationError(
                ("This account is not allowed here."),
                code='not_allowed',
            )

I would just like to use the inbuilt login form, like there is one for user registration, the login form normally only has the username and password fields

I would also like to know that if I redirect to another view Inside the login view, how can i save the current users data so that i can login the user using the built in log in function from another view function.

My current view Implementation:

views.py

def sendOTP(email):

    otp = None
    
    # send random otp to email
    otp = random.randint(100000, 999999)
    subject = 'RPi Security System OTP'
    message = 'Your OTP is ' + str(otp) + '. Please enter this OTP to login to your account.'
    from_email = 'example@example.com'
    recipient_list = [f'{email}']
    
    send_mail(subject, message, from_email, recipient_list)

    return otp


def login(request):
    if request.method == "POST":
        form = AuthenticationForm(request.POST)
        if form.is_valid():
            form.save()
            # authenticate user
            try:
                username = request.POST.get('username')
                password = request.POST.get('password')
                email = request.POST.get('email')
                user = authenticate(username=username, password=password)

                request.session['username'] = username
                request.session['email'] = email
            
            except:
                messages.error(request, f'Username {username} does not exist')
                return redirect("login")
            
            if user is not None:
                otp = sendOTP(email)

                if otp is not None:
                    request.session['otp_original'] = otp
                    return verifyOTP(request)
                
            else:
                messages.error(request, f'Username or password is incorrect')
                return redirect("login")
            
    else:
        form = AuthenticationForm()
        
    return render(request, 'users/login.html', {'form': form})

def verifyOTP(request):
    
    if request.method == "POST":
        otp = request.POST.get('otp')
        form = OTPForm(request.POST)
        if form.is_valid():
            form.save()
            if request.session['otp_original'] == int(otp):
                return redirect('fingerprint')
            else:
                messages.error(request, f'OTP is incorrect')
                return redirect('otp')
            
    else:
        form = OTPForm()
        
    return render(request, 'users/otp.html', {'form': form})

# Change for actual code
def fingerprint(request):

    
    return render(request, 'users/fingerprint.html')

First thing you don’t need to save the form in login form.save() because here you are only getting the data after form is submitted, this data is not going to be stored in your DB.

Now for your query, as I can see you first want to send the otp to the user then you want to login that user after otp is verified, so for storing the users current data like username you can use browser’s localstorage. Set username to localstorage once your work is done remove username from localstorage.

So I should maybe use the response.session variable to store the current username and password?

Because the login method requires a user object, will I have to create a new user object in the function which I actually need to login the user?

Or can I just store the user object directly?

On another note, thanks a lot for the help, it means a lot to me.

Can you also help me fix the login view, currently it is always returning “invalid form input”

views.py

def login(request):
    if request.method == "POST":
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            user = authenticate(request, username=username, password=password)

            if user is not None:
                login(request, user)
                email = user.email

                # Store user ID in session, not the user object
                request.session['user_id'] = user.id

                otp = sendOTP(email)
                request.session['otp_original'] = otp
                return redirect("success_page")  # Redirect to a success page or wherever needed
            else:
                messages.error(request, 'Invalid username or password')
        else:
            messages.error(request, 'Invalid form input')

    else:
        form = LoginForm()
        
    return render(request, 'users/login.html', {'form': form})

forms.py

class LoginForm(AuthenticationForm):
    username = UsernameField(label='Enter Username', widget=forms.TextInput(attrs={'class':'form-control'}))
    password = forms.CharField(label='Enter Password', widget=forms.PasswordInput(attrs={'class':'form-control'}))

Sorry for the many questions, i am new django user, so it is a bit difficult.

to see the actual error here^^ print(form.errors) in else condition.

One more thing if the topic is set to solved than kindly create a new topic for the other issues.