Reputation: 17392
I want django to authenticate users via email, not via usernames. One way can be providing email value as username value, but I dont want that. Reason being, I've a url /profile/<username>/
, hence I cannot have a url /profile/[email protected]/
.
Another reason being that all emails are unique, but it happen sometimes that the username is already being taken. Hence I'm auto-creating the username as fullName_ID
.
How can I just change let Django authenticate with email?
This is how I create a user.
username = `abcd28`
user_email = `[email protected]`
user = User.objects.create_user(username, user_email, user_pass)
This is how I login.
email = request.POST['email']
password = request.POST['password']
username = User.objects.get(email=email.lower()).username
user = authenticate(username=username, password=password)
login(request, user)
Is there any other of of login apart from getting the username first?
Upvotes: 138
Views: 140122
Reputation: 55
While @mipadi's EmailBackend solution works, I would argue it's not the cleanest approach. It forces Django to treat an email as a username, leading to confusing error messages, logs, and potential issues with third-party apps expecting actual usernames. A cleaner solution would be to create a proper email-based authentication by subclassing AbstractBaseUser:
# yourapp/models.py
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.contrib.auth.base_user import BaseUserManager
from django.db import models
class UserManager(BaseUserManager):
def create_user(self, email, password=None):
user = self.model(email=self.normalize_email(email))
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password=None):
user = self.create_user(email, password)
user.is_staff = True
user.is_superuser = True
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
Remember to set AUTH_USER_MODEL = 'yourapp.User'
in settings.py.
Upvotes: 0
Reputation: 1485
This solution is for apps that have already many users & do not want to migrate them to a new user table.
Get the user, call check_password
which returns True
if the form password matches the hashed password stored in auth_user
. Then all you need to do is call login(request, user)
. For example:
def login_user(request):
if request.method == "GET":
data = {
"form": UserForm,
}
return render(request, "login.html", data)
else:
email = request.POST["email"]
password = request.POST["password"]
user = User.objects.get(email=email)
if user:
passwords_match = user.check_password(password)
if passwords_match:
login(request, user)
logger.info(f"User with email {user.username} logged in")
return redirect("feed")
data = {
"form": UserForm,
"error": "Email & Password do not match"
}
return render(request, "login.html", data)
Then you need to alter the auth_user
table's email
column to include a unique constraint, I do this using a migration script:
# Generated by Django 5.0.6 on 2024-06-07 11:28
from django.db import migrations
from django.contrib.auth.models import User
from django.db import models
class Migration(migrations.Migration):
dependencies = [
('user', '0002_email_from_username'),
]
operations = [
migrations.RunSQL("""
ALTER TABLE auth_user
ADD CONSTRAINT unique_email_uc UNIQUE (email);
""")
]
Upvotes: 0
Reputation: 1
You can set up authentication with email
and password
instead of username
and password
and in this instruction, username
is removed and I tried not to change the default Django settings as much as possible. *You can also see my answer explaining how to extend User model with OneToOneField() to add extra fields and you can see my answer and my answer explaining the difference between AbstractUser and AbstractBaseUser.
First, run the command below to create account
app:
python manage.py startapp account
Then, set account
app to INSTALLED_APPS and set AUTH_USER_MODEL = 'account.User' in settings.py
as shown below:
# "settings.py"
INSTALLED_APPS = [
...
"account", # Here
]
AUTH_USER_MODEL = 'account.User' # Here
Then, create managers.py
just under account
folder and create UserManager
class extending (UM)UserManager in managers.py
as shown below. *Just copy & paste the code below to managers.py
and managers.py
is necessary to make the command python manage.py createsuperuser
work properly without any error:
# "account/managers.py"
from django.contrib.auth.models import UserManager as UM
from django.contrib.auth.hashers import make_password
class UserManager(UM):
def _create_user(self, email, password, **extra_fields):
if not email:
raise ValueError("The given email must be set")
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
return user
def create_user(self, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
if extra_fields.get("is_staff") is not True:
raise ValueError("Superuser must have is_staff=True.")
if extra_fields.get("is_superuser") is not True:
raise ValueError("Superuser must have is_superuser=True.")
return self._create_user(email, password, **extra_fields)
Then, create User
model extending AbstractUser and remove username
by setting it None
and set email
with unique=True and set email
to USERNAME_FIELD and set UserManager
to objects
in account/models.py
as shown below. *Just copy & paste the code below to account/models.py
:
# "account/models.py"
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import AbstractUser
from .managers import UserManager
class User(AbstractUser):
username = None # Here
email = models.EmailField(_("email address"), unique=True) # Here
USERNAME_FIELD = 'email' # Here
REQUIRED_FIELDS = []
objects = UserManager() # Here
Or, you can also create User
model extending AbstractBaseUser and PermissionsMixin as shown below. *This code below with AbstractBaseUser
and PermissionsMixin
is equivalent to the code above with AbstractUser
:
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.utils import timezone
from .managers import UserManager
class User(AbstractBaseUser, PermissionsMixin):
first_name = models.CharField(_("first name"), max_length=150, blank=True)
last_name = models.CharField(_("last name"), max_length=150, blank=True)
email = models.EmailField(_("email address"), unique=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
USERNAME_FIELD = 'email'
objects = UserManager() # Here
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
*Don't extend DefaultUser(User) model as shown below otherwise there is error:
# "account/models.py"
from django.contrib.auth.models import User as DefaultUser
class User(DefaultUser):
...
Then, create UserAdmin
class extending UA(UserAdmin) in account/admin.py
as shown below. *Just copy & paste the code below to account/admin.py
:
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.admin import UserAdmin as UA
from .models import User
@admin.register(User)
class UserAdmin(UA):
fieldsets = (
(None, {"fields": ("password",)}),
(_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
(
_("Permissions"),
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
),
},
),
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
)
add_fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": ("email", "password1", "password2"),
},
),
)
list_display = ("email", "first_name", "last_name", "is_staff")
ordering = ("-is_staff",)
readonly_fields=('last_login', 'date_joined')
Then, run the command below. *This must be the 1st migration to database when customizing User
model in this way otherwise there is error according to my experiments and the doc so before you develop your Django project, you must first create custom User
model:
python manage.py makemigrations && python manage.py migrate
Then, run the command below:
python manage.py createsuperuser
Then, run the command below:
python manage.py runserver 0.0.0.0:8000
Then, open the url below:
http://localhost:8000/admin/login/
Finally, you can log in with email
and password
as shown below:
And, this is Add custom user page as shown below:
Upvotes: 4
Reputation: 59
All of these are horrendously complicated for what should be a simple problem.
Check that a user exists with that email, then get that user's username for the argument in Django's authenticate()
method.
try:
user = User.objects.get(email = request_dict['email'])
user = authenticate(username = user.username, password = request_dict['password'])
except:
return HttpResponse('User not found.', status = 400)
Upvotes: 1
Reputation: 751
There are two main ways you can implement email authentication, taking note of the following:
A custom user model is recommended when starting a new project as changing mid project can be tricky.
We will add an email_verified
field to restrict email authentication to users with a verified email address.
# app.models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
email_verified = models.BooleanField(default=False)
We will then create a custom authentication backend that will substitute a given email address for a username.
This backend will work with authentication forms that explicitly set an email
field as well as those setting a username
field.
# app.backends.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
UserModel = get_user_model()
class CustomUserModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD, kwargs.get(UserModel.EMAIL_FIELD))
if username is None or password is None:
return
try:
user = UserModel._default_manager.get(
Q(username__exact=username) | (Q(email__iexact=username) & Q(email_verified=True))
)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
We then modify our projects settings.py
to use our custom user model and authentication backend.
# project.settings.py
AUTH_USER_MODEL = "app.User"
AUTHENTICATION_BACKENDS = ["app.backends.CustomUserModelBackend"]
Be sure that you run manage.py makemigrations
before you migrate
and that the first migration contains these settings.
While less performant than a custom User
model (requires a secondary query), it may be better to extend the existing User
model in an existing project and may be preferred depending on login flow and verification process.
We create a one-to-one relation from EmailVerification
to whichever User
model our project is using through the AUTH_USER_MODEL
setting.
# app.models.py
from django.conf import settings
from django.db import models
class EmailVerification(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_query_name="verification"
)
verified = models.BooleanField(default=False)
We can also create a custom admin that includes our extension inline.
# app.admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import EmailVerification
UserModel = get_user_model()
class VerificationInline(admin.StackedInline):
model = EmailVerification
can_delete = False
verbose_name_plural = 'verification'
class UserAdmin(BaseUserAdmin):
inlines = (VerificationInline,)
admin.site.unregister(UserModel)
admin.site.register(UserModel, UserAdmin)
We then create a backend similar to the one above that simply checks the related models verified
field.
# app.backends.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
UserModel = get_user_model()
class ExtendedUserModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD, kwargs.get(UserModel.EMAIL_FIELD))
if username is None or password is None:
return
try:
user = UserModel._default_manager.get(
Q(username__exact=username) | (Q(email__iexact=username) & Q(verification__verified=True))
)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
We then modify our projects settings.py
to use our authentication backend.
# project.settings.py
AUTHENTICATION_BACKENDS = ["app.backends.ExtendedUserModelBackend"]
You can then makemigrations
and migrate
to add functionality to an existing project.
Q(username__exact=username)
to Q(username__iexact=username)
.Upvotes: 30
Reputation: 17
The default user model inherits/ Extends an Abstract class. The framework should be lenient to a certain amount of changes or alterations.
A simpler hack is to do the following: This is in a virtual environment
LINE 336 on the email attribute add unique and set it to true
email = models.EmailField(_('email address'), blank=True,unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
Do this at you own risk,
Upvotes: -1
Reputation: 332
It seems that the method of doing this has been updated with Django 3.0.
A working method for me has been:
authentication.py # <-- I placed this in an app (did not work in the project folder alongside settings.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
class EmailBackend(BaseBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
def get_user(self, user_id):
UserModel = get_user_model()
try:
return UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
Then added this to the settings.py file
AUTHENTICATION_BACKENDS = (
'appname.authentication.EmailBackend',
)
Upvotes: 5
Reputation: 27
Pretty simple. There is no need for any additional classes.
When you create and update a user with an email, just set the username field with the email.
That way when you authenticate the username field will be the same value of the email.
The code:
# Create
User.objects.create_user(username=post_data['email'] etc...)
# Update
user.username = post_data['email']
user.save()
# When you authenticate
user = authenticate(username=post_data['email'], password=password)
Upvotes: -1
Reputation: 411132
You should write a custom authentication backend. Something like this will work:
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
Then, set that backend as your auth backend in your settings:
AUTHENTICATION_BACKENDS = ['path.to.auth.module.EmailBackend']
Updated. Inherit from ModelBackend
as it implements methods like get_user()
already.
See docs here: https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#writing-an-authentication-backend
Upvotes: 152
Reputation: 1343
I have created a helper for that: function authenticate_user(email, password)
.
from django.contrib.auth.models import User
def authenticate_user(email, password):
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
class LoginView(View):
template_name = 'myapp/login.html'
def get(self, request):
return render(request, self.template_name)
def post(self, request):
email = request.POST['email']
password = request.POST['password']
user = authenticate_user(email, password)
context = {}
if user is not None:
if user.is_active:
login(request, user)
return redirect(self.request.GET.get('next', '/'))
else:
context['error_message'] = "user is not active"
else:
context['error_message'] = "email or password not correct"
return render(request, self.template_name, context)
Upvotes: 4
Reputation: 951
If you’re starting a new project, django highly recommended you to set up a custom user model. (see https://docs.djangoproject.com/en/dev/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project)
and if you did it, add three lines to your user model:
class MyUser(AbstractUser):
USERNAME_FIELD = 'email'
email = models.EmailField(_('email address'), unique=True) # changes email to unique and blank to false
REQUIRED_FIELDS = [] # removes email from REQUIRED_FIELDS
Then authenticate(email=email, password=password)
works, while authenticate(username=username, password=password)
stops working.
Upvotes: 94
Reputation: 1
If You created Custom database, from there if you want to validate your email id and password.
models.objects.value_list('db_columnname').filter(db_emailname=textbox email)
2.assign in list fetched object_query_list
3.Convert List to String
Ex :
Take the Html Email_id
and Password
Values in Views.py
u_email = request.POST.get('uemail')
u_pass = request.POST.get('upass')
Fetch the Email id and password from the database
Email = B_Reg.objects.values_list('B_Email',flat=True).filter(B_Email=u_email)
Password = B_Reg.objects.values_list('Password',flat=True).filter(B_Email=u_email)
Take the Email id and password values in the list from the Query
value set
Email_Value = Email[0]
Password_Value=Password[0]
Convert list to String
string_email = ''.join(map(str, Email_Value))
string_password = ''.join(map(str, Password_Value))
Finally your Login Condition
if (string_email==u_email and string_password ==u_pass)
Upvotes: 0
Reputation: 755
Email authentication for Django 3.x
For using email/username and password for authentication instead of the default username and password authentication, we need to override two methods of ModelBackend class: authenticate() and get_user():
The get_user method takes a user_id – which could be a username, database ID or whatever, but has to be unique to your user object – and returns a user object or None. If you have not kept email as a unique key, you will have to take care of multiple result returned for the query_set. In the below code, this has been taken care of by returning the first user from the returned list.
from django.contrib.auth.backends import ModelBackend, UserModel
from django.db.models import Q
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try: #to allow authentication through phone number or any other field, modify the below statement
user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
except UserModel.DoesNotExist:
UserModel().set_password(password)
except MultipleObjectsReturned:
return User.objects.filter(email=username).order_by('id').first()
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
def get_user(self, user_id):
try:
user = UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
By default, AUTHENTICATION_BACKENDS is set to:
['django.contrib.auth.backends.ModelBackend']
In settings.py file, add following at the bottom to override the default:
AUTHENTICATION_BACKENDS = ('appname.filename.EmailBackend',)
Upvotes: 38
Reputation: 128
Authentication with Email For Django 2.x
def admin_login(request):
if request.method == "POST":
email = request.POST.get('email', None)
password = request.POST.get('password', None)
try:
get_user_name = CustomUser.objects.get(email=email)
user_logged_in =authenticate(username=get_user_name,password=password)
if user_logged_in is not None:
login(request, user_logged_in)
messages.success(request, f"WelcomeBack{user_logged_in.username}")
return HttpResponseRedirect(reverse('backend'))
else:
messages.error(request, 'Invalid Credentials')
return HttpResponseRedirect(reverse('admin_login'))
except:
messages.warning(request, 'Wrong Email')
return HttpResponseRedirect(reverse('admin_login'))
else:
if request.user.is_authenticated:
return HttpResponseRedirect(reverse('backend'))
return render(request, 'login_panel/login.html')
Upvotes: 0
Reputation: 31
Authentication with Email and Username For Django 2.x
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
class EmailorUsernameModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
In settings.py, add following line,
AUTHENTICATION_BACKENDS = ['appname.filename.EmailorUsernameModelBackend']
Upvotes: 3
Reputation: 702
Email and Username Authentication for Django 2.X
Having in mind that this is a common question, here's a custom implementation mimicking the Django source code but that authenticates the user with either username or email, case-insensitively, keeping the timing attack protection and not authenticating inactive users.
from django.contrib.auth.backends import ModelBackend, UserModel
from django.db.models import Q
class CustomBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
except UserModel.DoesNotExist:
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
def get_user(self, user_id):
try:
user = UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
Always remember to add it your settings.py the correct Authentication Backend.
Upvotes: 6
Reputation: 1
For Django 2
username = get_object_or_404(User, email=data["email"]).username
user = authenticate(
request,
username = username,
password = data["password"]
)
login(request, user)
Upvotes: 0
Reputation: 436
from django.contrib.auth.models import User
from django.db import Q
class EmailAuthenticate(object):
def authenticate(self, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(email=username) | Q(username=username))
except User.DoesNotExist:
return None
except MultipleObjectsReturned:
return User.objects.filter(email=username).order_by('id').first()
if user.check_password(password):
return user
return None
def get_user(self,user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And then in settings.py
:
AUTHENTICATION_BACKENDS = (
'articles.backends.EmailAuthenticate',
)
where articles is my django-app, backends.py
is the python file inside my app and EmailAuthenticate
is the authentication backend class inside my backends.py
file
Upvotes: 1
Reputation: 1475
I had a similar requirement where either username/email should work for the username field.In case someone is looking for the authentication backend way of doing this,check out the following working code.You can change the queryset if you desire only the email.
from django.contrib.auth import get_user_model # gets the user_model django default or your own custom
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
# Class to permit the athentication using email or username
class CustomBackend(ModelBackend): # requires to define two functions authenticate and get_user
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
# below line gives query set,you can change the queryset as per your requirement
user = UserModel.objects.filter(
Q(username__iexact=username) |
Q(email__iexact=username)
).distinct()
except UserModel.DoesNotExist:
return None
if user.exists():
''' get the user object from the underlying query set,
there will only be one object since username and email
should be unique fields in your models.'''
user_obj = user.first()
if user_obj.check_password(password):
return user_obj
return None
else:
return None
def get_user(self, user_id):
UserModel = get_user_model()
try:
return UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
Also add AUTHENTICATION_BACKENDS = ( 'path.to.CustomBackend', ) in settings.py
Upvotes: 14
Reputation: 1381
You should customize ModelBackend class. My simple code:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
class YourBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
if '@' in username:
UserModel.USERNAME_FIELD = 'email'
else:
UserModel.USERNAME_FIELD = 'username'
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
And in settings.py file, add:
AUTHENTICATION_BACKENDS = ['path.to.class.YourBackend']
Upvotes: 2