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.
Related
I've overridden the ModelBackend in my django app. My overridden model backend requires that headers be present in the request to log in the user.
HEADER = 'testing'
class TestingModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
testing_header_value = None
if request is not None and request.META is not None:
testing_header_value = request.META.get(HEADER, None)
if username is None:
username = kwargs.get(User.USERNAME_FIELD)
try:
user = User.objects.get_by_natural_key(username, testing_header_value)
except User.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
User().set_password(password)
else:
# now validate password and whether the user is active
if user.check_password(password) and self.user_can_authenticate(user):
return user
This works perfectly in non test scenarios. However, when I test I'm running into a problem of passing headers with the test client.
The Django test client has a login method but it doesn't pass the request when authenticating which means that my model backend can't function correctly - I can't pass the header I need to. Note that one of the parameters in the authenticate function is the current request.
I see that I can use force_login but that seems a little hack-y. What is the correct way to do this? I suspect subclassing the default test client and overriding the login method might be best but I'm not sure.
I believe force_login() is the best thing to use in your case.
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 :)
I am kind of new to django and I am concerned about can we use multiple models for authentication for different types of users for our application e.g for Customers use Customer model, for Suppliers use Supplier model and also keep default User registration model for administration use only? If so can you point me in the right direction, how it can be done? Which package should be used?
There is one way that I came around is by adding a foreign key to each model viz needed for authentication but that will involve joins in every query which could result in performance issues. I need to know if there is a better way. An also these custom models can benefit for all permissions stuff available in admin panel.
Expert opinion will be really appreciated.
There are a few different options to handle that.
Maybe check first the Django-docu.
I've you'ld like to customize it to authenticate your users with a mail-address, here is an example written by knbk and rahul-gupta on the ex-documentation:
Django's default authentication works on username and password fields. Email authentication backend will authenticate users based on email and password.
from django.contrib.auth import get_user_model
class EmailBackend(object):
"""
Custom Email Backend to perform authentication via email
"""
def authenticate(self, username=None, password=None):
user_model = get_user_model()
try:
user = user_model.objects.get(email=username)
if user.check_password(password): # check valid password
return user # return user to be authenticated
except user_model.DoesNotExist: # no matching user exists
return None
def get_user(self, user_id):
user_model = get_user_model()
try:
return user_model.objects.get(pk=user_id)
except user_model.DoesNotExist:
return None
Add this authentication backend to the AUTHENTICATION_BACKENDS setting.
# settings.py
AUTHENTICATION_BACKENDS = (
'my_app.backends.EmailBackend',
...
)
I'm using Django 1.8.4 on Python 3, and attempting to create an auth backend which validates a cookie from a legacy ColdFusion web site and create / log the Django user in after checking the value in a database. In settings, I am including the backend:
AUTHENTICATION_BACKENDS = (
'site_classroom.cf_auth_backend.ColdFusionBackend',
)
And the code for the backend itself; SiteCFUser is a model against the SQL Server database user model which contains the active cookie token value:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from users.models import SiteCFUser
class ColdFusionBackend(ModelBackend):
"""
Authenticates and logs in a Django user if they have a valid ColdFusion created cookie.
ColdFusion sets a cookie called "site_web_auth"
Example cookie: authenticated#site+username+domain+8E375588B1AAA9A13BE03E401A02BC46
We verify this cookie in the MS SQL database 'site', table site_users, column user_last_cookie_token
"""
def authenticate(self, request):
User = get_user_model()
print('Hello!')
token=request.COOKIES.get('site_web_auth', None)
print('Token: ' + token)
cookie_bites = token.split('+')
if cookie_bites[0] != "authenticated#site":
# Reality check: not a valid site auth cookie
return None
username = cookie_bites[1]
cf_token = cookie_bites[3]
try:
site_user = SiteCFUser.objects.using('mssqlsite').filter(cf_username=username)
except:
# No user found; redirect to login page
return None
if site_user[0].cftoken == cf_token:
try:
# Does the user exist in Django?
user = User.objects.get(username=username)
except:
# User does not exist, has a valid cookie, create the User.
user = User(username=username)
user.first_name = site_user[0].cf_first_name
user.last_name = site_user[0].cf_last_name
user.email = site_user[0].cf_email
user.save()
else:
return None
def get_user(self, user_id):
User = get_user_model()
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
The problem is, the backend doesn't seem to be called when hitting a URL with a view with #login_required, or even trying to log in through a form with username and password. If I force an error by changing the name of the class in settings, or change the name of the class in cf_auth_backend.py, I do get an error. However, none of the print statements show up in the console. I'm clearly missing something here: any idea what I'm not doing right?
While the accepted answer might have helped the OP, it's not a general answer to the question's title.
Authentication back ends do work simply by listing them in AUTHENTICATION_BACKENDS. But they may appear to be ignored
for various reasons, e.g.:
urls.py needs to point to something like django.contrib.auth.views.login
url(r'^accounts/login/$', django.contrib.auth.views.login)
if it's pointing to some other authentication app. AUTHENTICATION_BACKENDS
may not work.
the authenticate() method must accept a password keyword, either through
password=None or **kwargs. Probably true for username too. It won't
be called if it doesn't accept that keyword argument.
Authentication backends doesn't work that way. They won't be called on each request or on requests where authentication is required.
If you want to log in user based on some cookie, you should call authentication in middleware.
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?