I've been nearly done with my django-react app with all the models, serializers, and APIs. But now I need to change the authentication method to also use email.
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(unique=True)
# Notice there is no username field, because it is included in AbstractUser
I've looked through some possible solutions but they involve using AbstractBaseUser while some other write a custom authentication backend that omits the username completely. This might break other views and frontend since we have been mainly using username.
I still want to keep the username and use both username and email to authenticate.
Is there any simple idea (preferably kept using AbstractUser) that I wouldn't have to make major change?
This code is taken from the book “Django 3 by Example”, chapter 4, section “Building a custom authentication backend”.
Django provides a simple way to define your own authentication backends. An authentication backend is a class that provides the following two methods: authenticate() and get_user().
Suppose you have the account app in your project to manage the authentication logic. You can create the authentication.py file with the following code:
class EmailAuthBackend(object):
"""
Authenticate using an e-mail address.
"""
def authenticate(self, request, username=None, password=None):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
return None
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
The preceding code works as follows:
authenticate(): You try to retrieve a user with the given email address and check the password using the built-in check_password() method of the user model. This method handles the password hashing to compare the given password with the password stored in the database.
get_user(): You get a user through the ID provided in the user_id parameter. Django uses the backend that authenticated the user to retrieve the User object for the duration of the user session.
Edit the settings.py file of your project and add the following setting:
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'account.authentication.EmailAuthBackend',
]
In the preceding setting, you keep the default ModelBackend that is used to authenticate with the username and password and include your own email-based authentication backend.
Django will try to authenticate the user against each of the backends, so now you should be able to log in seamlessly using your username or email account. User credentials will be checked using the ModelBackend authentication backend, and if no user is returned, the credentials will be checked using your custom EmailAuthBackend backend.
Related
I use Django with Django Rest Framework as the backend for my site. Registration is disabled, and login with password is disabled. The only way a user can register and login is with Django Social Auth, that exchanges (in this case Discord) a social token for a Django token, and in that process the user is created if they don't exist for that email.
So in Django, the user exists, with a username and email, but they don't have a password.
How can these users login to the admin panel?
Registration is disabled, and login with password is disabled.
Registration is not the only way of creating users. You can create superuser using createsuperuser shell command. Then you can use it to login into admin site.
Now, the question is if you want to allow all users to admin site. IMHO, you should not. Still, if you want to then you can add a custom auth backend, like this:
class CustomBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return None
check_token = Token.objects.filter(user=user, token=token).exists()
if check_token:
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And add the new backend in settings.py:
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend', 'path.to.CustomBackend']
I have a model called Van that have 2 fields: Plate and Password. I should use this informations to login in a frontend system (not django admin).
Searching on the internet i just found example using Bultin Login System but i need a custom login using only Van model.
Any suggestions?
You need to implement custom authorization backend.
For example you can modify this:
from django.contrib.auth.backends import ModelBackend
class EmailAuthBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
except ObjectDoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
User().set_password(password)
Is it possible to use Django allauth with the authentication method set to 'email' when using it on multiple sites?
I'm aiming to allow a user with the email address bob#example.com to create an account at site1.com and a separate account at site2.com.
In order to use email authentication, I need to leave UNIQUE_EMAIL set to True in the settings but this prevents users who already have accounts in one site from creating accounts in the other site.
I am assuming you'd like to allow the same email to be registered separately for each of the sites in your Django setup.
Looking at the allauth code; it appears that it is infeasible to do so at the moment, likely because allauth does not take into account site ID as part of the User signup process.
class AppSettings(object):
class AuthenticationMethod:
USERNAME = 'username'
EMAIL = 'email'
USERNAME_EMAIL = 'username_email'
class EmailVerificationMethod:
# After signing up, keep the user account inactive until the email
# address is verified
MANDATORY = 'mandatory'
# Allow login with unverified e-mail (e-mail verification is
# still sent)
OPTIONAL = 'optional'
# Don't send e-mail verification mails during signup
NONE = 'none'
def __init__(self, prefix):
self.prefix = prefix
# If login is by email, email must be required
assert (not self.AUTHENTICATION_METHOD ==
self.AuthenticationMethod.EMAIL) or self.EMAIL_REQUIRED
# If login includes email, login must be unique
assert (self.AUTHENTICATION_METHOD ==
self.AuthenticationMethod.USERNAME) or self.UNIQUE_EMAIL
One way to do this would be as follows:
- Keep allauth AUTHENTICATION_METHOD as Username
- Store the site alongside the User information, perhaps in a UserProfile or by overriding the User Model.
- Make the combination of Email and Site unique.
- Override the LoginView such that the user enters email; you can translate the combination of Email, Site to a Unique User account and username; which you can pass on to allauth to perform login.
Assuming you use the Sites framework; your code would look something like this:
from allauth.account.views import LoginView
from django.core.exceptions import ObjectDoesNotExist
class CustomLoginView(LoginView):
def get_user():
email = request.POST.get('email')
current_site = Site.objects.get_current()
try:
user = User.objects.get(email=email, site=current_site)
except ObjectDoesNotExist:
pass # Handle Error: Perhaps redirect to signup
return user
def dispatch(self, request, *args, **kwargs):
user = self.get_user()
request.POST = request.POST.copy()
request.POST['username'] = user.username
return super(CustomLoginView, self).dispatch(request, *args, **kwargs)
Then monkey-patch the LoginView with the custom login view:
allauth.account.views.LoginView = CustomLoginView
Related Reading on setting up a Site FK, and custom auth backends:
How to get unique users across multiple Django sites powered by the "sites" framework?
https://docs.djangoproject.com/en/dev/topics/auth/#writing-an-authentication-backend
How to write a custom authentication backend in Django taking scenario as Phone Number & OTP(One-Time Password) to authenticate against each user.
How to authenticate each user in form of multiple conditions.
If email is verified and password exist ( authenticate using email and password).
If phone is verified and exist( authenticate using phone and otp or if password exist then auth using phone and password).
from django.contrib.auth import backends, get_user_model
from django.db.models import Q
class AuthenticationBackend(backends.ModelBackend):
"""
Custom authentication Backend for login using email,phone,username
with password
"""
def authenticate(self, username=None, password=None, **kwargs):
usermodel = get_user_model()
try:
user = usermodel.objects.get(
Q(username__iexact=username) | Q(email__iexact=username) | Q(phone__iexact=username)
if user.check_password(password):
return user
except usermodel.DoesNotExist:
pass
For you have to specify the authclass in settings.py
AUTHENTICATION_BACKENDS = (
'applications.accounts.auth_backends.AuthenticationBackend',
)
There are many ways to extend user model, here I leave you this page and you can choose which of them is better for you https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html
My custom user model:
class MyUser(AbstractBaseUser):
username = models.CharField(unique=True,max_length=30)
email = models.EmailField(unique=True,max_length=75)
is_staff = models.IntegerField(default=False)
is_active = models.IntegerField(default=False)
date_joined = models.DateTimeField(default=None)
# Use default usermanager
objects = UserManager()
USERNAME_FIELD = 'email'
Is there a way to specify multiple USERNAME_FIELD ? Something like ['email','username'] so that users can login via email as well as username ?
The USERNAME_FIELD setting does not support a list. You could create a custom authentication backend that tries to look up the user on the 'email' or 'username' fields.
from django.db.models import Q
from django.contrib.auth import get_user_model
MyUser = get_user_model()
class UsernameOrEmailBackend(object):
def authenticate(self, username=None, password=None, **kwargs):
try:
# Try to fetch the user by searching the username or email field
user = MyUser.objects.get(Q(username=username)|Q(email=username))
if user.check_password(password):
return user
except MyUser.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
MyUser().set_password(password)
Then, in your settings.py set AUTHENTICATION_BACKENDS to your authentication backend:
AUTHENTICATION_BACKENDS = ('path.to.UsernameOrEmailBackend,)\
Note that this solution isn't perfect. For example, password resets would only work with the field specified in your USERNAME_FIELD setting.
We can do that by implementing our own Email authentication backend.
You can do something like below:
Step-1 Substite the custom User model in settings:
Since we would not be using Django's default User model for authentication, we need to define our custom MyUser model in settings.py. Specify MyUser as the AUTH_USER_MODEL in the project's settings.
AUTH_USER_MODEL = 'myapp.MyUser'
Step-2 Write the logic for the custom authentication backend:
To write our own authentication backend, we need to implement atleast two methods i.e. get_user(user_id) and authenticate(**credentials).
from django.contrib.auth import get_user_model
from django.contrib.auth.models import check_password
class MyEmailBackend(object):
"""
Custom Email Backend to perform authentication via email
"""
def authenticate(self, username=None, password=None):
my_user_model = get_user_model()
try:
user = my_user_model.objects.get(email=username)
if user.check_password(password):
return user # return user on valid credentials
except my_user_model.DoesNotExist:
return None # return None if custom user model does not exist
except:
return None # return None in case of other exceptions
def get_user(self, user_id):
my_user_model = get_user_model()
try:
return my_user_model.objects.get(pk=user_id)
except my_user_model.DoesNotExist:
return None
Step-3 Specify the custom authentication backend in settings:
After writing the custom authentication backend, specify this authentication backend in the AUTHENTICATION_BACKENDS setting.
AUTHENTICATION_BACKENDS contains the list of authentication backends to be used. 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.
AUTHENTICATION_BACKENDS = (
'my_app.backends.MyEmailBackend', # our custom authentication backend
'django.contrib.auth.backends.ModelBackend' # fallback to default authentication backend if first fails
)
If authentication via MyEmailBackend fails i.e user could not be authenticated via email, then we use the Django's default authentication ModelBackend which will try to authenticate via username field of MyUser model.
No, you cannot have more than one field defined in USERNAME_FIELD.
One option would be to write your own custom login to check for both fields yourself. https://docs.djangoproject.com/en/1.8/topics/auth/customizing/
i.e. change the backend to your own. AUTHENTICATION_BACKENDS then write an authenticate method and check the username on both fields in the DB.
PS you may want to use unique_together on your model so you don't run into problems.
Another option would be to use the actual field username to store both string and email.
Unfortunately, not out-of-the box.
The auth contrib module asserts that the USERNAME_FIELD value is mono-valued.
See https://github.com/django/django/search?q=USERNAME_FIELD
If you want to have a multi-valued USERNAME_FIELD, you will either have to write the corresponding logic or to find a package that allow it.
If your USERNAME_FIELD is username and the user logs in with email, maybe you can write a code that fetches the username using the provided email and then use that username along with the password to authenticate.
REQUIRED_FIELDS = []
you can define multiple username_fields