When using the standard authentication module in django, a failed user authentication is ambiguous. Namely, there seems to be no way of distinguishing between the following 2 scenarios:
Username was valid, password was invalid
Username was invalid
I am thinking that I would like to display the appropriate messages to the user in these 2 cases, rather than a single "username or password was invalid...".
Anyone have any experience with simple ways to do this. The crux of the matter seems to go right to the lowest level - in the django.contrib.auth.backends.ModelBackend class. The authenticate() method of this class, which takes the username and password as arguments, simply returns the User object, if authentication was successful, or None, if authentication failed. Given that this code is at the lowest level (well, lowest level that is above the database code), bypassing it seems like a lot of code is being thrown away.
Is the best way simply to implement a new authentication backend and add it to the AUTHENTICATION_BACKENDS setting? A backend could be implemented that returns a (User, Bool) tuple, where the User object is only None if the username did not exist and the Bool is only True if the password was correct. This, however, would break the contract that the backend has with the django.contrib.auth.authenticate() method (which is documented to return the User object on successful authentication and None otherwise).
Maybe, this is all a worry over nothing? Regardless of whether the username or password was incorrect, the user is probably going to have to head on over to the "Lost password" page anyway, so maybe this is all academic. I just can't help feeling, though...
EDIT:
A comment regarding the answer that I have selected:
The answer I have selected is the way to implement this feature. There is another answer, below, that discusses the potential security implications of doing this, which I also considered as the nominated answer. However, the answer I have nominated explains how this feature could be implemented. The security based answer discusses whether one should implement this feature which is, really, a different question.
You really don't want to distinguish between these two cases. Otherwise, you are giving a potential hacker a clue as to whether or not a username is valid - a significant help towards gaining a fraudulent login.
This is not a function of the backend simply the authentication form. Just rewrite the form to display the errors you want for each field. Write a login view that use your new form and make that the default login url. (Actually I just saw in a recent commit of Django you can now pass a custom form to the login view, so this is even easier to accomplish). This should take about 5 minutes of effort. Everything you need is in django.contrib.auth.
To clarify here is the current form:
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
username = forms.CharField(label=_("Username"), max_length=30)
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
def __init__(self, request=None, *args, **kwargs):
"""
If request is passed in, the form will validate that cookies are
enabled. Note that the request (a HttpRequest object) must have set a
cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
running this validation.
"""
self.request = request
self.user_cache = None
super(AuthenticationForm, self).__init__(*args, **kwargs)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username and password:
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive."))
elif not self.user_cache.is_active:
raise forms.ValidationError(_("This account is inactive."))
# TODO: determine whether this should move to its own method.
if self.request:
if not self.request.session.test_cookie_worked():
raise forms.ValidationError(_("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."))
return self.cleaned_data
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
Add:
def clean_username(self):
username = self.cleaned_data['username']
try:
User.objects.get(username=username)
except User.DoesNotExist:
raise forms.ValidationError("The username you have entered does not exist.")
return username
We had to deal with this on a site that used an external membership subscription service. Basically you do
from django.contrib.auth.models import User
try:
user = User.objects.get(username=whatever)
# if you get here the username exists and you can do a normal authentication
except:
pass # no such username
In our case, if the username didn't exist, then we had to go check an HTPASSWD file that was updated by a Perl script from the external site. If the name existed in the file then we would create the user, set the password, and then do the auth.
This answer is not specific to Django, but this is the pseudo-code I would use to accomplish this:
//Query if user exists who's username=<username> and password=<password>
//If true
//successful login!
//If false
//Query if user exists who's username=<username>
//If true
//This means the user typed in the wrong password
//If false
//This means the user typed in the wrong username
def clean_username(self):
"""
Verifies that the username is available.
"""
username = self.cleaned_data["username"]
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return username
else:
raise forms.ValidationError(u"""\
This username is already registered,
please choose another one.\
""")
Related
I am creating an app that authenticates with users for AD, but then it also needs separate passwords to authenticate for two other services. Is there a way to use 3 passwords to verify login? I can set up log in verification individually for each service, but I was wondering if I could store all three passwords in the same session. Reason being is because users will need to authenticate with multiple services to use all functions of this app.
here is roughly what I am doing in my view.py
request.session['pass_kinit2030'] = password
request.session['reg_pass'] = reg_pass
request.session['oraclepass'] = oraclepass
I can see several options:
If all your 3 passwords are equal, I suppose you just define your own AUTHENTICATION_BACKEND in Django. In such backend you would just take the password and check if it's valid in all three services.
In yourapp/auth.py:
from django.contrib.auth.backends import BaseBackend
class MyAuthBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
# Check reg and oracle - if valid, return request.user, else return None
In settings.py:
AUTHENTICATION_BACKENDS = ['yourapp.auth.MyAuthBackend']
But since you said separate passwords, I suppose they are not equal.
In case passwords are not equal, it is questionable to ask for 2 other passwords in this single app, in the first place. You shouldn't ask the user for 2 other passwords in the login form. Normally, you should authenticate your other two apps using some kind of token. You then connect your apps with such token in a different view than login view. Having such token, you should keep its value in a new model that has OneToOneField relationship to the user:
from django.conf import settings
from django.db import models
class UserTokens(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
reg_token = forms.CharField(
null=True,
max_length=100)
oracle_token = models.CharField(
null=True,
max_length=100)
You might as well use a password as a token, as long as the password is generated in a similar way to a token (and cannot be changed to a value given by the user - I know such cases).
Last but not least, you may not like the idea of tokens or may not have them implemented in related two apps. Then the question is, how these two apps are accessed? Are they HTTP servers? If yes, you are all set. The browser should keep the login session to two other apps separately. And, still you can redirect to these apps / iframe them or do JavaScript GETs or POSTs, if we talk about API of some kind - yes JS requests will use the login session.
I ended up using multiple try/except methods to testing the logging in on the other other services.
try:
try:
a = add_user().login(username=creds['user'], password=creds['banpass'])
add_user().logout(a)
except:
messages.error(request, 'Banner Password Is Incorrect')
return redirect('login')
if Usermanager(creds=creds).test_login():
pass
else:
messages.error(request, 'Wrong Regular Account Credentials')
return redirect('login')
if user is not None:
auth.login(request, user)
return redirect('userinfo')
else:
messages.error(request, 'Wrong AdminCredentials')
return redirect('login')
I am having difficulty understanding how Django's documentation has outlined the overriding of the authenticate method in contrib.auth.models.Users. According to the code below from here, wouldnt the authenticate method succeed if the method was passed a valid username and a valid hash that exists anywhere in the database regardless of whether it matches the password for the supplied primary key field (username, email, etc...) or not. Is there something that check_password is doing that I am not seeing like ensuring that the field that was passed alongside of the password is checked behind the scenes? Because this supplied example appears to have a flaw.
# From Django 1.10 Documentation
def authenticate(self, username=None, password=None):
login_valid = (settings.ADMIN_LOGIN == username)
pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
if login_valid and pwd_valid:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# Create a new user. There's no need to set a password
# because only the password from settings.py is checked.
user = User(username=username)
user.is_staff = True
user.is_superuser = True
user.save()
return user
return None
Thanks.
authenticate() function returns user for which you attach session using login()
Use authenticate() to verify a set of credentials. It takes
credentials as keyword arguments, username and password for the
default case, checks them against each authentication backend, and
returns a User object if the credentials are valid for a backend. If
the credentials aren’t valid for any backend or if a backend raises
PermissionDenied, it returns None.:
In case of the following authentication backend username and password are passed to it.
Password is compared with one set in Django settings and user object
is queried from database.
If user with that username does not exist backend creates new user.
This backend works even though from security aspect it is not best one :)
CONTEXT:
A model object's user_account_granted account indicates if a model object is linked to an non null user account.
When user_account_granted changes from False to True, I detect this in the overridden save() function. Here, I successfully make a User, pulling arguments (email, username, name, etc) from the model object
I create a password and send the new account login info to the object's email
If the email failed, I delete the account
PROBLEM:
I want to alert the current user (who just submitted the form triggering the save() ) that the email was either successful (and the new account now exists) or unsuccessful (and no new account is created). I cannot use the Django Messaging Framework in the save() function, as it requires the request. What can I do?
def save(self, *args, **kwargs):
if self.id:
previous_fields = MyModel(pk=self.id)
if previous_fields.user_account_granted != self.user_account_granted:
title = "Here's your account!"
if previous_fields.user_account_granted == False and self.user_account_granted == True:
user = User(
username=self.first_name + "." + self.last_name,
email=self.email,
first_name=self.first_name,
last_name=self.last_name
)
random_password = User.objects.make_random_password() #this gets hashed on user create
user.set_password(random_password)
user.save()
self.user = user
message = "You have just been given an account! \n\n Here's your account info: \nemail: " + self.user.email + "\npassword: " + random_password
if previous_fields.user_account_granted == True and self.user_account_granted == False:
message = "You no longer have an account. Sorry :( "
try:
sent_success = send_mail(title, message, 'example#email.com', [self.email], fail_silently=False)
if sent_success == 1:
##HERE I WANT TO INDICATE EMAIL SENT SUCCESS TO THE USER'S VIEW AFTER THE FORM IS SUBMITTED
else:
##HERE I WANT TO INDICATE EMAIL SENT FAILURE TO THE USER'S VIEW AFTER THE FORM IS SUBMITTED
user.delete()
self.user_account_granted = False
except:
##HERE I WANT TO INDICATE EMAIL SENT FAILURE TO THE USER'S VIEW AFTER THE FORM IS SUBMITTED
user.delete()
self.user_account_granted = False
super(MyModel, self).save(*args, **kwargs)
You don't send an email in a model's save() function. Ever. That's just not its purpose. Consider:
save() may be called from the shell.
save() may be called from wherever in your project.
The purpose of the save() method is to save the object. End of story.
Now. Let's get back to what you really want to achieve. You are in the process of handling a form submitted by the user. Which means you're dealing with at least to other things here: the form and the view.
Let's have a closer look at what their purpose is:
The Form's basic role is to encapsulate data input and validation. It can be extended so as to encompass the full role of a Command. After all, it only misses an execute() function.
The View's basic role is to take action based on the browser's request (here, the POSTing of a form) and trigger the displaying of the result.
You may choose either. You could have an execute() method on your form, and just call that from you view. Or you could have the view take action after checking that the form is_valid(). I would personnaly choose the form for the actual action, and the view for showing the result.
Thus, in my view, I would customize the form_valid() method to call execute() and do whatever is needed depending on the result. That is:
class MyForm(Form):
# whatever you already have there
def clean(self):
# unrelated to your issue, but just reminding you
# this is the place you make sure everything is right
# This very method MUST raise an error if any input or any
# other condition already known at that point is not fulfilled.
if you_do_not_want_to_grand_an_account:
raise ValidationError('Account not granted, because.')
return self.cleaned_data
def execute(self):
do_whatever()
needs_to_be_done()
if it_failed:
raise AnAppropriateError(_('Descriptive message'))
class MyView(FormView):
form = MyForm
# your other view stuff here
def form_valid(self, form):
try:
form.execute()
except AnAppropriateError as err:
messages.add_message(self.request, messages.ERROR, err.message)
else:
messages.add_message(self.request, messages.INFO, _('It worked'))
AnAppropriateError may just be RuntimeError if you want it quick and dirty, but you should define your own exception class.
Also, you may want to decorate the execute()̀ method with#transaction.atomic()`, depending on its content.
As a final note, remember you cannot be sure the email was actually sent. It's common to have mail systems that accept emails even if there is some error. You'll only know when it bounces, several days later.
First of all it is really not a good idea to send email from model classes. I highly object that. It is better to do that in your form
But you have already implemented that.
There is a way you can access the request inside a model, I wouldn't recommend it though. There is a plugin called CRequest Middlewar, https://pypi.python.org/pypi/django-crequest
You can use this middleware to access the request anywhere new like.
$ python pip install django-crequest
First import the crequest’s middleware:
from crequest.middleware import CrequestMiddleware
Get the current request ;):
current_request = CrequestMiddleware.get_request()
Done. Now you can use the message framework as you wish in that model.
Maybe a dumb question, but if possible, what's the best way of writing a BasePasswordHasher subclass using username as a part of the salt? I'm rewriting a site from scratch and used this approach in php. The problem is accessing the username in a password hasher. I would be really happy to resolve this as a LOT of users would lose their passwords otherwise, so a big thanks in advance!
PHP Code:
function passHash($login, $pass)
{
return md5(md5($pass).'salt'.$login);
}
As you noticed, this cannot be done in the password hasher alone. The password hasher does not have information about the user, only the password and hash. I think you have two options.
First, and probably best, is to write a custom authentication backend. At the authentication backend level, we have access to the username and raw password. It would look like this
# settings.py
AUTHENTICATION_BACKENDS=(
'myapp.backends.LegacyBackend',
'django.contrib.auth.backends.ModelBackend',
)
# myapp.backends
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.utils.encoding import force_bytes
import hashlib
class LegacyBackend(ModelBackend):
# We only need to override the authenticate method
def authenticate(self, username=None, password=None, **kwargs):
# most of this is copied directly from ModelBackend's authenticate method
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
# This is the normal route that hands off to the password hasher pipeline
# but we will sidestep it entirely and verify the password here
#
# if user.check_password(password):
# return user
pwhash = hashlib.md5(force_bytes(password)).hexdigest()
hash = hashlib.md5(force_bytes(pwhash+"salt"+username)).hexdigest()
if hash == user.password:
# update the user's password if you want, so you can phase out this backend
user.set_password(password)
user.save(update_fields=["password"])
return user
except UserModel.DoesNotExist:
UserModel().set_password(password)
Note that I haven't tested this code, but it should work as advertised. In addition, you don't have conflicts with new users, and old users will have their passwords updated to the new hashing algorithm (default is PBKDF2+SHA256? not sure).
The second option is to write a one-off script to modify your database so the user.password fields look like legacymd5$username+salt$hash. Then you can write your custom password hasher as planned.
For anyone who finds this post as I did, everything still works as expected, except one thing. On Django 2.1, I found I had to add "request" as the first argument in the authenticate method. They must have added passing this thru at some point. I was silently failing the auth and had no idea why.
i want to authenticate users using firstname and lastname
This is the code i am using
user = auth.authenticate(first_name=firstname,last_name=lastname,password=password)
it keep coming up with NoneType: None
i have checked the firstname and lastname plus password seen to be correct?
what i am doing wrong? thanks
The difficulty here is that normally you'd handle this by creating a custom authentication backend that implements authenticate and get_user. However, the function signature for authenticate is:
def authenticate(self, username=None, password=None):
Everywhere in Django that would be calling this will be passing only 2 parameters, username and password. This means that using any of the generic authentication forms and things like the admin interface will break if this is done any other way.
The only work around I could see, and this is kind of sketchy, is if the username were to be typed as a single entry with a string "First Last" (delimited by a space) in place of the username. You could then separate it out and use that value...
(this is all untested, but you get the idea)
class FirstLastNameBackend(object):
def authenticate(self, username=None, password=None):
first, last = username.split(' ', 1)
try:
user = User.objects.get(first_name=first, last_name=last)
if user:
# Check if the password is correct
# check if the user is active
# etc., etc.
return user
except:
pass
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except:
return None
The django doc provides a lot of helpful details on doing a custom backend: User auth with custom backend
On a side note, something to be careful of is last names that have a space(s) in them, like "de la Cruz". If you specify 1 for maxsplit on the split function, you'll avoid this problem.
Building a little from #T. Stone's idea. Why not have them register with their First and Last name and you just concatenate them together and use that as their username?. And everytime you have them login you setup your view to combine the two fields again and use that string.
You won't be able to use some of the auto forms they can produce for you but that's not a big deal. I'd just combine the two strings, lowercase them and slap that as the username and do the same for every login instance.
You can use any parameters in backend authentication function, i.e.:
class FirstLastNameBackend(object):
def authenticate(self, first_name=None, last_name=None, password=None):
pass #your user auth code goes here
In order to authenticate user you call
user = auth.authenticate(first_name=firstname,
last_name=lastname,
password=password)
One drawback, however, that you'll need to implement your own log in form and this authentication won't be supported in admin interface.
I like the idea of not making users remember a username, but I think a better solution to that is to have their email address be their user name. Is it fair for you to assume in your specific application that no two users will have the same first and last name? If that's not a fair assumption, how will your system handle that?