'How to lock out users when they input wrong password three time in python django

I try to write a login page that will lock the user by inputting the wrong password three times and the username will go to the blacklist so that it will be locked. The login page works well and the blacklist works well. One problem is the loop does not work, I had while count < 3 in the beginning, but it only gives the user one chance to input password, then I rewrite the code as if ... elif... format to check what goes wrong. What I find is it stuck on "1 Username or Password is incorrect 1" which means it only goes to the first if and the count always is 1 which means the count goes back 0 every time.

I think that because after the user clicks the login button, the page refresh and makes the count 0 again, so how should I solve it?

@unauthenticated_user

def loginPage(request):

if request.method == "POST":
    username = request.POST.get('username')  # Get username input first
    password = request.POST.get('password')
    user = authenticate(request, username=username, password=password)
    BL = BlackList.objects.values_list('list', flat=True)  # Read all data into array
    if username in BL:  # Check if the username is in blacklist
        messages.info(request, 'Username in black list, please contact admin')
    else:  # Not in black list username can go to login
        count = 0
        if count == 0:  # User can try 3 times for each login in
            if user is not None:
                login(request, user)
                return redirect('home')
            else:
                count += 1
                messages.info(request, '1 Username or Password is incorrect' + str(count))

        elif count == 1:
            messages.info(request, 'testest' + str(count))
            request.method == "POST"
            username = request.POST.get('username')  # Get username input first
            password = request.POST.get('password')
            user = authenticate(request, username=username, password=password)
            if user is not None:
                login(request, user)
                return redirect('home')
            else:
                count += 1
                messages.info(request, '2 Username or Password is incorrect' + str(count))
        elif count == 2:
            request.method == "POST"
            username = request.POST.get('username')  # Get username input first
            password = request.POST.get('password')
            user = authenticate(request, username=username, password=password)
            if user is not None:
                login(request, user)
                return redirect('home')
            else:
                count += 1
                messages.info(request, '3 Username or Password is incorrect' + str(count))

        else:  # 3 times fail the username will go to the black list
            BlackList.objects.create(list=username)
            # Put the username in to BlackList
            messages.info(request, 'Username in black list, please contact admin')

context = {}
return render(request, 'accounts/login.html', context)


Solution 1:[1]

You can store count in the session.

if request.session.get('count', 0) == 0:
   request.session['count'] = 1
else:
   request.session['count'] += 1

   if request.session['count'] == 3:
       pass       # ban him

Solution 2:[2]

If your blacklist is not saved in a database or a text file in the server, the user will be able to bypass your line of defense by refreshing the web. Therefore you have to store your blacklist outside runtime.

The most effective way to do this is by creating a simple text file that saves the blacklisted usernames, and that file will be stored in the server. This also stops that username from logging in with another device.

Try replacing your code with this one.

if request.method == "POST":
    username = request.POST.get('username')  # Get username input first
    password = request.POST.get('password')
    user = authenticate(request, username=username, password=password)
            
    with open("blacklist.txt", "a") as BlackL:
        pass


    with open("blacklist.txt") as BlackL:
        for BL in Black:
            if username == BL: #This one checks if username in blacklist
                return "User name in blacklist, contact admin."
    
            else:  # Not in black list username can go to login
                count = 0
                if count == 0:  # User can try 3 times for each login in
                    if user is not None:
                        login(request, user)
                        return redirect('home')
                    else:
                        count += 1
                        messages.info(request, '1 Username or Password is incorrect' + str(count))

                elif count == 1:
                    messages.info(request, 'testest' + str(count))
                    request.method == "POST"
                    username = request.POST.get('username')  # Get username input first
                    password = request.POST.get('password')
                    user = authenticate(request, username=username, password=password)
                    if user is not None:
                        login(request, user)
                        return redirect('home')
                    else:
                        count += 1
                        messages.info(request, '2 Username or Password is incorrect' + str(count))
                else count == 2:
                    request.method == "POST"
                    username = request.POST.get('username')  # Get username input first
                    password = request.POST.get('password')
                 user = authenticate(request, username=username, password=password)
                    if user is not None:
                        login(request, user)
                        return redirect('home')
                    else:
                        count += 1
                        with open("blacklist.txt", "a") as BlackL:
                            print(username, file=BlackL)
                        return "User name in blacklist, contact admin."


context = {}
return render(request, 'accounts/login.html', context)

I couldn't run the code in my computer but I believe you can debug it by yourself if there are any bugs.

Solution 3:[3]

Since this appeared quite high on Google, and I don't see an appropriate solution, I'll provide mine here:

The best place for this information to live is most likely in the user model in the database.

In my user model:

class SystemUser(AbstractUser):
    logins_failed("Number of failed login attempts", default=0)

    ...

Then you can add to this counter everytime a correct username is provided with a wrong password. And stop logins from users with too many attempts.

In views.py

def login(request):
    if request.method == "POST":
        form = LoginForm(request.POST)
        
        if form.is_valid()
            unauthenticated_user = None
            user = None
            
            try:
                unauthenticated_user = SystemUser.objects.get(email=form.cleaned_data["username"].lower())
                user = authenticate(request, username=unauthenticated_user.username, password=form.cleaned_date["password"])
            except SystemUser.DoesNotExist:
                # We pass this error, as we will handle it below with added logic for failed logins-count
                pass

        if not user:
            form.add_error(field="username", error="Wrong password or unknown username. Please try again.")
             
             # If user does not exist, but we have an unauthenticated_user
             # It means the provided password was wrong
             if unauthenticated_user:
                 unauthenticated_user.logins_failed += 1
                 unauthenticated_user.save()

        elif user.logins_failed >= 3:
            # If the user exists but have too many attempts we inform the user that they're locked out
            form.add_error(field=None, error="You're locked out of the system due to too many attempts. Contact your admin.")

        else:
            # Username and password is correct, we allow the user to login
            login(request, user)
            
            # Remember to reset the failed logins-count
            user.logins_failed = 0
            user.save()

            return redirect(reverse("homepage"))

    else:
        # If it wasn't a POST-request we send the user to the empty login-page
        form = SystemUserLoginForm()
    
    return render(request, "userlogin.html", {"form": form})

As you may have noticed this solution will continue counting wrong password-attempt beyond three, as long as the user provides a wrong password. Only when the user provides the correct password will they be informed that they are locked out. This has the advantage of not exposing our users to potential intruders who are testing with different email addresses.

Remember to add functionality for an administrator to reset this counter, to allow users to login again.

Also consider your approach to a potential "Reset password"-functionality. Should blocked users be allowed to reset their password? If so you must also reset the counter in this process.

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 Pramote Kuacharoen
Solution 2 SiemGhirmai
Solution 3