Django, How authenticate user with first name and last name? - python

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?

Related

Django not REST - API user registration

I have my first project as junior in my work. It is old (Django 1.8) and it is normal django framework... not REST.
It supports web, and mobile.
I have to create endpoint for mobile to create user.
I think it is not a problem (to create) but I want to make sure it will be save.
First of all I thought that I will create normal ModelForm (RegisterAPIForm based on model=User with full validation (I mean init all fields that are in "backend" not visible for user | cleaned_data for all fields | special overwriten method save() that in addition hashes password, and send email) and in Views I'll add something like this:
class RegistrationAPITestView(View):
def post(self, request):
form = RegistrationAPIForm(
request.POST
)
if form.is_valid():
form.save()
return JsonResponse({})
else:
#check errors and send error code back
Or I should do it other way, by using User object?
class RegistrationAPITestView(View):
def post(self, request):
#check if user does not exist
#password1 and password2 validation
user = User.objects.create()
user.name = request.POST['username']
user.set_password(request.POST['password'])
#init all fields that user can't choose like groups etc
user.save()
What do you think? Do I need ModelForm that I won't even render? It seems to be safer, but maybe I should check it other way? with User object?
Btw. Registration form already exists for web but there is a lot of "web" stuff that I don't need and I don't have to check and there is another method of saving password, so I believe I should create new one.
My code will be revieved in 2 weeks (senior vacations) but now I'm alone and want to do my best.
There is nothing wrong with the second option, but here is the problem that you as a junior should avoid. This line will make the server return a 500 error request.POST['username'] because python will throw a key error if the user doesn't provide the username, to fix just change to request.POST.get('username', 'value if doesn\'t exit') also make sure that everything is ready before create the user or you will have records in the database that wont be useful. Call validators to the password too and try to cover all possible scenario. Remember never trust the user

Django authenticate function that accepts extra fields

That django authenticate function accepts two parameters, eg
user = authenticate(username=username, password=password)
, but I would like to pass in an additional parameter to validate the entry. Is that possible to do so or can the authenticate function be overridden to achieve that?
Please advise, thanks.
authenticate is just a python function you can use *args & **kwargs to pass multiple params and you can override the authenticate method like (How to override authenticate method ) to add your own validation there.
As far as I know, the authenticate() function takes any keyword arguments as credentials. It simply forwards the credentials to the configured authentication backends. So it really depends on the authentication backend what you do with the passed arguments.
This means you can pass whatever you want to the authenticate() function, but you might need to implement your own authentication backend. You can achieve that by specyfing custom auth backends in the settings.py:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'confirm.django.auth.backends.EmailBackend',
)
Here's the code for the EmailBackend:
from django.contrib.auth.backends import ModelBackend, UserModel
class EmailBackend(ModelBackend): # pylint: disable=too-few-public-methods
'''
Authentication backend class which authenticates the user against his
e-mail address instead of his username.
'''
def authenticate(self, username=None, password=None, **kwargs): # pylint: disable=unused-argument
'''
Authenticate the user via his e-mail address.
:param str username: The username used to authenticate
:param str password: The password used to authenticate
:return: When successful, the User model is returned
:rtype: User model or None
'''
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
return None
When a user is logging in with an e-mail address, the credentials will be passed to the ModelBackend.authenticate() method, which will fail because there's no username matching the e-mail address. After that, the EmailBackend.authenticate() method is called, which will succeed (if the password matches). The first backend returning a User instance "wins".
So it depends what you want to do.
If you simply want to allow users logging in with either their username or e-mail address (or any other field), then this is the way to go. Just create a new auth backend and add it to the AUTHENTICATION_BACKENDS, together with django.contrib.auth.backends.ModelBackend.
If you need an additional field verified for the login, let's say a triplet like customer ID, username & password, you need to create your own backend as well. However, this time the backend should be the only one configured in the AUTHENTICATION_BACKENDS, thus replacing django.contrib.auth.backends.ModelBackend with it.
If you want to know more about customizing authentication, just head over to the brilliant docs.

Django password hasher with username in hash

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.

Migrating a password field to Django

I have used Django before (version 1.2) and generally I like it... it is especially good at getting a brand new project up and running quickly. But, in this case, I'm rewriting and existing system and moving it to Python/Django. So, I already have a MySQL database that has a "users" table in it... this table stores the user's password with the MySQL SHA1 function (no salt, etc).
As part of the migration, I'm going to fix some of the data modeling flaws and port to PostgreSQL.
I would really like to use django.contrib.auth, but I'm unclear what I need to do. I have read the documentation, and know that I can separate the required user information and the "extra" information I have and put it into UserProfile.
But, how to handle the passwords stored in the MySQL db?
Has anyone handled this before? What approach did you take?
Here is what I did to get things working. I created a custom authentication backend. Note: I'm using the email address as the username.
Here is my code:
from django.db.models import get_model
from django.contrib.auth.models import User
from hashlib import sha1
class MyUserAuthBackend(object):
def check_legacy_password(self, db_password, supplied_password):
return constant_time_compare(sha1(supplied_password).hexdigest(), db_password)
def authenticate(self, username=None, password=None):
""" Authenticate a user based on email address as the user name. """
try:
user = User.objects.get(email=username)
if '$' not in user.password:
if self.check_legacy_password(user.password, password):
user.set_password(password)
user.save()
return user
else:
return None
else:
if user.check_password(password):
return user
except User.DoesNotExist:
return None
def get_user(self, user_id):
""" Get a User object from the user_id. """
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Then I added the following to settings.py:
AUTHENTICATION_BACKENDS = (
'my_website.my_app.my_file.MyUserAuthBackend',
)
The suggestion from #Dougal appears to be for the next release of Django and was not available for me (I'm using 1.3.1). However, it seems like it will be a better solution.
You can probably put it straight into the user_password field - see the Django docs. Since you don't have a salt, try using the format sha1$$password_hash. I haven't investigated to see that it'll work with a blank salt, but that's probably the only way you're going to be able to migrate it without hacking django.contrib.auth or writing your own authentication backend.
Otherwise, you could just set an unusable password (the canonical thing to do is set the field to !) for users and point them to the forgot-password system.
Recent versions of Django provide a hasher for unsalted legacy passwords. Just add this to your settings file:
PASSWORD_HASHERS = (
...
'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher',
)

Separately validating username and password during Django authentication

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.\
""")

Categories