'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 |
