Enable Login with Email in Django
Django is currently my favorite framework for building web applications. Despite its simplicity and delightful development experience, one of the things that I personally don’t like about Django is its default authentication system. I don’t know why they’re doing this, but for me, log in with username doesn’t meet the web industry standards.
Apparently, this problem can be fixed solved easily with some hacks. Let’s take a look at the steps on how to enable your users to login with their emails in your Django apps.
Getting Started
Create a new Django project. This can be done by creating a new virtual environment, install Django, and generate a new project with django-admin
command.
$ python -m venv env
$ source env/bin/activate
$ pip instal django
$ django-admin startproject django_email_login .
I have published the entire source code of this project on my GitHub. Feel free to poke around or clone it onto your computer with the command below.
$ git clone https://github.com/rahmanfadhil/django_email_login.git
Also, don’t forget to install the dependencies.
$ pip install -r requirements.txt
Custom user model
We can start by creating a new application in our Django project which called accounts
. This app will contain everything we need for our user authentication features.
$ python manage.py startapp accounts
Just to remind you guys, make sure to install your app in your settings.py
each time you start a new one.
# ...
INSTALLED_APPS = [
# other apps...
'accounts.apps.AccountsConfig'
]
In order to make our email
field required in our database, we need to define a custom user model. Let’s do it inside our accounts
application.
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import gettext_lazy as _
class CustomUser(AbstractUser):
email = models.EmailField(_('email address'), unique=True)
It’s also important to make our email
field unique.
Then, add the AUTH_USER_MODEL
variable in our settings.py
to tell Django we’re going to use a custom model for our users.
# ...
AUTH_USER_MODEL = 'accounts.CustomUser' # new
Once we created a new model, make sure to generate the migrations and run them.
$ python manage.py makemigrations
$ python manage.py migrate
Authentication backend
The next step is to create a new custom authentication backend for our app. This allows us to override the default logic of our authentication system since we’re trying to allow our users to log in with their email or username.
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q
UserModel = get_user_model()
class EmailBackend(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)
return
except UserModel.MultipleObjectsReturned:
user = UserModel.objects.filter(Q(username__iexact=username) | Q(email__iexact=username)).order_by('id').first()
if user.check_password(password) and self.user_can_authenticate(user):
return user
Now, let’s modify the settings.py
to register our custom authentication backend.
# ...
AUTH_USER_MODEL = 'accounts.CustomUser'
AUTHENTICATION_BACKENDS = ['accounts.backends.EmailBackend'] # new
Authentication forms
Once we have modified the authentication system, we also need to adjust our forms to make sure our views ask the right data.
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth import get_user_model
from django import forms
class RegisterForm(UserCreationForm):
class Meta:
model = get_user_model()
fields = ('email', 'username', 'password1', 'password2')
class LoginForm(AuthenticationForm):
username = forms.CharField(label='Email / Username')
By default, the UserCreationForm
only takes the username and password fields (with its verification input). So, we can modify this by creating a new class called RegisterForm
which extends the default form. Then, we specify the required fields to create a new user (email, username, and password).
The login form on the other hand only needs a little adjustment. We need to change the field type of our username
field so that it can accept both email and username.
From now on, we will use these forms to signup a new user, and log in an existing one.
Views
This part is optional, but in case you still confused on how to implement this feature in your view, you can follow these steps.
Create the views for login and register. Here, we can use our custom authentication forms a and pass the template where we render those forms.
from django.contrib.auth import views as auth_views
from django.views import generic
from django.urls import reverse_lazy
from .forms import LoginForm, RegisterForm
class LoginView(auth_views.LoginView):
form_class = LoginForm
template_name = 'accounts/login.html'
class RegisterView(generic.CreateView):
form_class = RegisterForm
template_name = 'accounts/register.html'
success_url = reverse_lazy('login')
We also want to redirect the users to our login page right after the signed up a new account by defining success_url
property in our RegisterView
.
Let’s create some templates that show the login and register form.
<h1>Login</h1>
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" id="submit">Login</button>
</form>
<h1>Register</h1>
<form action="{% url 'register' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" id="submit">Register</button>
</form>
Then, we can register our views in urls.py
.
from django.contrib import admin
from django.urls import path
from accounts.views import LoginView, RegisterView
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', LoginView.as_view(), name='login'),
path('register/', RegisterView.as_view(), name='register')
]
That’s it!
Run your app with the command below, and here is what you should get.
$ python manage.py runserver
Login:
Register:
Tags: Python, Django, Authentication