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',
...
)
Related
I'm trying to implement a custom authentication backend in Django but its behaving in a very strange way. The custom backend authenticate against a different model from the usual Django User model an upon successfully verifying the provided credentials the backend get or create our usual Django User instance which it returns. Login nevertheless doesn't work.
From the documentation I learnt that I just needed to inherit the django.contrib.auth.backends.BaseBackend and override the authenticate() method which I did. As I've mentioned the custom authenticate() method basically verifies a set of credentials (username and password) against ones stored in the database (custom model, not django.contrib.auth.models.User) which if matched it would get or create an instance of django.contrib.auth.models.User via a proxy model which I named Profile; basically the Profile model has a reference of both django.contrib.auth.models.User and my custom model. When logging in though I keep redirected to the login page (It's like Django logs me in but doesn't set something somewhere such that when I try accessing a protected resource I'm redirected back to login). Also when I login which a django.contrib.auth.models.User object it works just fine and I can access the protected pages. Following are the reasons I opted for this authentication approach.
I'm working with an existing database that has it's own User tables with very different schema than what Django provides.(Actually the only fields in this system User table similar to Django's are username, and password)
I utilized Django's inspectb management command to recreate the models of which I'm under strict instructions to leave unmanaged, you know, manage=False in the model's meta class. Basically I can't inherit Django's AbstractUser as it would require new fields to be added.
Thus the best approach I could think of was to use a proxy model that would link both django.contrib.auth.models.User and my custom unmanaged models with a custom Authentication Backend.
I've tried watching for certain variables such as the request.session dictionary and request.user all which are set but still can't login successfully. Also when I use credentials that are not in either of the two models I get a Invalid Credentials message in the login page (desirable behavior).
Actually, my custom authenticate() method works fine and returns a valid user, the issue I think lies in django.contrib.auth.login. What could be the problem?
Here is the my authenticate method
def authenticate(self, request, username=None, password=None):
try:
c_user = CustomUser.objects.get(username=username)
except CustomUser.DoesNotExist:
return None
#pwd_valid = check_password(password, user.password)
if not c_user.password==password:
return None
#Get and return the django user object
try:
profile = Profile.objects.get(custom_user=c_user)
user = profile.user
#Create user if profile has none
if not user:
user = User.objects.create(
username=''.join(secrets.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(24))
)
profile.user = user
profile.save()
#Create new profile if none exists
except Profile.DoesNotExist:
#Create new user for the profile
user = User.objects.create(
username=''.join(secrets.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(24))
)
Profile.objects.create(
user = user,
custom_user = c_user
)
return user
Here is how I added the custom backend to settings.py
AUTHENTICATION_BACKENDS = [
'Authentication.custom.CustomAuth',
'django.contrib.auth.backends.ModelBackend'
]
Can't believe I'm answering my own question. After much debugging and reading documentation I realized I didn't override get_user method of the django.contrib.auth.backends.BaseBackend thus the default one which returns None was always called. I implemented it and it worked like a charm.
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')
In my project, I should create personal type user and company type user they have different attributes. I can create them with different models but I cannot implement both in auth system at the same time? I wonder if it is possible?
As stated in django docs you can write your own AUTHENTICATION_BACKENDS.
For example:
class UserProfileModelBackend(ModelBackend):
def authenticate(self, username=None, password=None):
try:
user = self.user_class.objects.get(username=username)
if user.check_password(password):
return user
except self.user_class.DoesNotExist:
return None
def get_user(self, user_id):
try:
return self.user_class.objects.get(pk=user_id)
except self.user_class.DoesNotExist:
return None
#property
def user_class(self):
if not hasattr(self, '_user_class'):
self._user_class = apps.get_model(*settings.AUTH_USER_MODEL.split('.', 2))
if not self._user_class:
raise ImproperlyConfigured('Could not get custom user model')
return self._user_class
And then add this auth-backend to in AUTHENTICATION_BACKENDS in settings.py.
For more information see Writing an authentication backend
When somebody calls django.contrib.auth.authenticate() Django tries authenticating across all of its authentication backends. If the first authentication method fails, Django tries the second one, and so on, until all backends have been attempted.
Be careful:
The order of AUTHENTICATION_BACKENDS matters, so if the same username and password is valid in multiple backends, Django will stop processing at the first positive match.
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 have an application written in Django and I have to extend it and include some other solution as an "app" in this application.
For example, my app to be integrated is named "my_new_app"
Now there is a backend authentication written for the main application and I cannot use it.
I have a MySQL DB to query from and the main app uses Cassandra and Redis mostly.
Is there any way I can use a separate authentication backend for the new app "my_new_app" and run both in the same domain?
You can have multiple authentication backends. Just set the AUTHENTICATION_BACKENDS in settings.py of your Django project to list the backend implementations you want to use. For example I often use a combination of OpenID authentication and the standard Django authentication, like this in my settings.py:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'django_openid_auth.auth.OpenIDBackend',
)
In this example Django will first try to authenticate using django.contrib.auth.backends.ModelBackend, which is the default backend of Django. If that fails, then it moves on to the next backend, django_openid_auth.auth.OpenIDBackend.
Note that your custom backends must be at a path visible by Django. In this example I have to add django_openid_auth to INSTALLED_APPS, otherwise Django won't be able to import it and use it as a backend.
Also read the relevant documentation, it's very nicely written, easy to understand:
https://docs.djangoproject.com/en/dev/topics/auth/customizing/
I've been through this problem before. This is the code I used.
This is the authentication backend at the api/backend.py
from django.contrib.auth.models import User
class EmailOrUsernameModelBackend(object):
def authenticate(self, username=None, password=None):
if '#' in username:
kwargs = {'email': username}
else:
kwargs = {'username': username}
try:
user = User.objects.get(**kwargs)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And this is my settings.py
AUTHENTICATION_BACKENDS = (
'api.backend.EmailOrUsernameModelBackend',
'django.contrib.auth.backends.ModelBackend',
)
This code will enable you to use email to authenticate the default Django user even in Django admin.
Using multiple backend authentications is as simple as pie. You just need to understand the workflow of Django apps.
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.Backend1',
'django_openid_auth.auth.Backend2',
)
For example you have the following two backends defined. Django will first go to the first backend and you just need to put some logic in that backend so that, if its not related to that backend it get forwarded to the other backend or returned without any results. In case of no results django will automatically shift the request from first backend to the second and if available third one.
I spend a lot of time on this and found out that it was not that complex.
Using Multiple AUTHENTICATION BACKENDS is very easy,
you just need to add this to settings.py
AUTHENTICATION_BACKENDS = (
'social_core.backends.open_id.OpenIdAuth',
'social_core.backends.google.GoogleOpenId',
'social_core.backends.google.GoogleOAuth2',
'social_core.backends.google.GoogleOAuth',
'social_core.backends.facebook.FacebookOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
and this may create a problem at signup page so add a login argument in your signup view in views.py file like this
login(request, user, backend='django.contrib.auth.backends.ModelBackend')
def signup_view(request):
if request.method=='POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user=form.save()
login(request, user, backend='django.contrib.auth.backends.ModelBackend')
return redirect('home')
Done a little safety and reassemble for code. cause provided solutions wasn't perfectly matching necessary.
my version for the previous: https://stackoverflow.com/a/17078818/14972653
with my addition and #M.Void comment:
This is the authentication backend at the api/backend.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.core.validators import validate_email
user_model = get_user_model()
class EmailOrUsernameModelBackend(ModelBackend):
"""EmailOrUsernameModelBackend - UsernameOrEmail custom authentication backend
with email or username validation on same field
provides case insensitive validation of username and email
BEWARE - if you allow users to register two usernames in differren cases like: Alex and alex - there would be error.
still uses ModelBackend so provides permissions and is_active validation
"""
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(user_model.USERNAME_FIELD)
if username is None or password is None:
return
try:
validate_email(username)
except ValidationError as e:
kwargs = {'username__iexact': username} # remove __iexact to make it case sensitive
else:
kwargs = {'email__iexact': username} # remove __iexact to make it case sensitive
try:
user = user_model.objects.get(**kwargs)
except user_model.DoesNotExist:
return None
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
def get_user(self, user_id):
try:
return user_model.objects.get(pk=user_id)
except user_model.DoesNotExist:
return None
and of course add it to your authentication backends:
AUTHENTICATION_BACKENDS = (
'api.backend.EmailOrUsernameModelBackend',
'django.contrib.auth.backends.ModelBackend',
)