Django Authentication – How to build Login/Logout/Signup for custom User
Database name : atikgohel
you can change database name : DjangoAuth\settings.py line number 85
Building user User authentication is not easy, in almost case, it’s complicated. Fortunately, Django has a powerful built-in User authentication that helps us create our Authentication system fast. By default, the User model in Django auth
app contains fields: username, password, email, first_name, last_name… However, using our own custom user model allows us deal with user profile more comfortably. For example, what if we want to add more fields: full_name or age?
In this tutorial, we’re gonna look at way to customize authentication in Django (version 3.1.7) using subclass of AbstractBaseUser
: AbstractUser
. All User authentication data will be stored in MySQL database that we’ll show you how to config the datasource.
We will build a Dajngo Project with Authentication app that has login/logout/signup with custom fields such as full name and age:
We will code our custom signup()
function, login()
and logout()
is automatically implemented by Django auth
.
All User data will be saved in MySQL/PostgreSQL database.
Here is the folders and files structure that we will create in the next steps.
Create a manually highlighted folder and file
1. authen\apps.py
2. authen\forms.py
3. static folder
4. static\css folder -> bootstrap.css
5. static\js folder -> bootstrap.js
6. templates floder
7. templates\auth folder
8. templates\auth\base.html
9. templates\auth\index.html
10.templates\auth\login.html
11.templates\auth\signup.html
! Go to the following link and copy and paste it It's just a bootstrap file !
– bootstrap.css
– bootstrap.js
Create Django project named DjangoAuth with command:
django-admin startproject DjangoAuth
Run following commands to create new Django App named authen inside the project:
– cd DjangoAuth
– python manage.py startapp authen
Open authen\apps.py, we can see AuthenConfig
class (subclass of the django.apps.AppConfig
) that represents our Django app and its configuration:
from django.apps import AppConfigclass AuthenConfig(AppConfig): name; = 'authen'
Open settings.py, find INSTALLED_APPS
, then add:
INSTALLED_APPS = [ ... 'authen.apps.AuthenConfig', ]
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, ... }, ]
STATIC_URL = '/static/'• MySQL DatabaseSTATICFILES_DIRS = [ BASE_DIR / "static" ]
Install & Import Python MySQL Client
We have to install Python MySQL Client to work with MySQL database.
In this tutorial, we use pymysql: pip install pymysql
.
Once the installation is successful, import this module in DjangoAuth/__init__.py:
import pymysql pymysql.install_as_MySQLdb()
• Setup MySQL Database engine
Open settings.py and change declaration of DATABASES
:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'atikGohel', 'USER' : 'root', 'PASSWORD' : '', 'HOST': 'localhost', 'PORT' : '3306', } }• Create a new Custom User Model
In authen/models.py, create a new User model called CustomUser
that extends AbstractUser
(a subclass of AbstractBaseUser), then add two custom fields: full_name
and age
:
from django.db import models from django.contrib.auth.models import AbstractUser class CustomUser(AbstractUser): full_name = models.CharField(max_length=100, blank=False) age = models.PositiveIntegerField(null=True, blank=True)
age
field uses both null
and blank
:
– null
is for database. null=True
indicates that we can store it in database entry as NULL (no value).
– blank
is for validation. blank=True
accepts empty value for the form field, so blank=False
indicates that the value is required.
• Specify Custom User Model in setting.py
In setting.py, we add AUTH_USER_MODEL
config to specify our custom user model instead of Django built-in User
model. The model is named CustomUser
and exists within authen
app, so we refer to it as authen.CustomUser
:
AUTH_USER_MODEL = 'authen.CustomUser'
Now we create a new file in the authen app called forms.py:
from django import forms from django.contrib.auth.forms import UserCreationForm, UserChangeForm from .models import CustomUserclass SignUpForm(UserCreationForm): full_name = forms.CharField(max_length=100, help_text='Required. 100 charaters of fewer.') class Meta: model = CustomUser fields = UserCreationForm.Meta.fields + ('full_name', 'age',)
Our SignUpForm
extends the UserCreationForm
.
We set model
to CustomUser
and use default fields by Meta.fields
which includes all default fields (including username, first_name, last_name, email, password, groups…). We simply plus our custom fields (full_name
, age
) at the end and it will display automatically on signup page.
When a user signs up for a new account, the default form only asks for a username
, email
, and password
. Now, it also requires full_name
and age
.
Now our new database model is created, we need to update Django in 2 steps:
• Create migration file
Run the command:
python manage.py makemigrations authen
We can see output text:
Migrations for 'authen': authen\migrations\0001_initial.py - Create model CustomUser
It indicates that the authen/migrations/0001_initial.py file includes code to create CustomUser data model:
# Generated by Django 3.1.7 on 2021-04-06 18:05 import django.contrib.auth.models import django.contrib.auth.validators from django.db import migrations, models import django.utils.timezone class Migration(migrations.Migration): initial = True dependencies = [ ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ migrations.CreateModel( name='CustomUser', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('full_name', models.CharField(max_length=100)), ('age', models.PositiveIntegerField(blank=True, null=True)), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ], options={ 'verbose_name': 'user', 'verbose_name_plural': 'users', 'abstract': False, }, managers=[ ('objects', django.contrib.auth.models.UserManager()), ], ), ]
The generated code defines a subclass of the django.db.migrations.Migration
. It has an operation for creating CustomUser
model table. Call to migrations.CreateModel()
method will create a table that allows the underlying database to persist the model.
You can see that we have not only user default fields but also custom fields (full_name
, age
).
• Generate database table
Run the following Python script to apply the generated migration:
python manage.py migrate
The output text:
Operations to perform: Apply all migrations: admin, auth, authen, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0001_initial... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK
A warning may come to you, ignore it 👇👇
System check identified some issues:WARNINGS: ?: (mysql.W002) MariaDB Strict Mode is not set for database connection 'default' HINT: MariaDB's Strict Mode fixes many data integrity problems in MariaDB, such as data truncation upon insertion, by escalating warnings into errors. It is strongly recommended you activate it. See: https://docs.djangoproject.com/en/3.1/ref/databases/#mysql-sql-mode
Operations to perform: Apply all migrations: admin, auth, authen, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0001_initial... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK
Check MySQL Database, for example, now we can see that a table for CustomUser model was generated and it’s named authen_customuser
:
• Set url patterns
Open the project-level DjangoAuth\urls.py file
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('authen.urls')), ]
Open the app-level authen\urls.py file, we’re gonna use built-in django.contrib.auth
module to handle login/logout requests:
from django.urls import path from django.contrib.auth import views as auth_views from authen import views urlpatterns = [ path('', views.home, name="home"), path('login/', auth_views.LoginView.as_view(template_name='auth/login.html'),name='login'), path('logout/', auth_views.LogoutView.as_view(), name='logout'), path('signup/', views.signup, name="signup") ]
Using auth_views.LogoutView
helps us handle logout request automatically, we only need to call {% url 'logout' %}
where we want to make logout event in the HTML template.
The next step is to specify where to redirect the user upon a successful login/logout.
Open project setting.py, then set values for LOGIN_REDIRECT_URL
and LOGOUT_REDIRECT_URL
:
LOGIN_REDIRECT_URL = 'home' LOGOUT_REDIRECT_URL = 'home'
Now, after login/logout, if we don’t indicate where to come, the user will be redirected to the 'home'
template which is our homepage.
• Custom signup request
Inside authen/views.py, define functions for handling signup request and homepage:
from django.shortcuts import render, redirect from django.contrib.auth import login, authenticate from .forms import SignUpForm def home(request): return render(request, 'auth/home.html') def signup(request): if request.method == 'POST': form = SignUpForm(request.POST) if form.is_valid(): user = form.save() user.save() raw_password = form.cleaned_data.get('password1') user = authenticate(username=user.username, password=raw_password) login(request, user) return redirect('home') else: form = SignUpForm() return render(request, 'auth/signup.html', { 'form' : form })
Now we dive into signup()
function. It gets user data from HTTP POST request which is handled by SignUpForm
, save user to database.
Then we use authenticate()
function and login()
function from django.contrib.auth
to log the user in.
If the process is successful, redirect to homepage, otherwise, return to signup.html
template.
• Edit or Create base.html
In templates\auth folder, edit or create new HTML file named base.html:
The most powerful – and thus the most complex – part of Django’s template engine is template inheritance. Template inheritance allows you to build a base “skeleton” template that contains all the common elements of your site and defines blocks that child templates can override.
it's template inheritance
<!DOCTYPE html> {% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> {% block title %}{% endblock title %} | Atik Gohel </title>
<link rel="stylesheet" href="{% static 'css/bootstrap.css'%}">
</head>
<body>
<div class="container mt-5">
<h3 class="text-center alert alert-danger"> Login and Singup Example </h3>
</div>
{% block body %}{% endblock body %}
<script src=" {% static 'js/bootstrap.js' %}"> </script>
</body>
</html>
• Edit or Create home.html
We’re gonna use Django built-in url templatetag to create links for login/logout/signup requests.
In templates\auth folder, edit or create new HTML file named home.html:
{% extends 'auth/base.html' %}
{% block title %}Home{% endblock title %}
{% block body %}
<div class="container">
<div class="row">
<div class="clo-12">
<div class="alert alert-info">this is home page</div>
{% if user.is_authenticated %}
Hi {{ user.full_name }}, Welcome to My site!
<a href="logout/" class="btn btn-danger">Logout</a>
{% else %}
You are not logged in!
<br>
<a href="{% url 'login' %}" class="btn btn-primary mt-3">
Log In
</a>
<a href="{% url 'signup' %}" class="btn btn-primary mt-3">
SignUp
</a>
{% endif %}
</div>
</div>
</div>
{% endblock body %}
If you use {% extends %}
in a template, it must be the first template tag in that template. Template inheritance won’t work, otherwise.
{% block title %}{% endblock title %} it is title block
{% block body %}{% endblock body %} it is title block
We can use is_authenticated
attribute to specify whether the user is logged in or not, then show his full name for a website greeting.
So, when user aren’t logged in, it looks like:
• Edit or Create Django custom Signup.html
– We’re gonna use HTML <form>
tag with HTTP POST method.
– We add {% csrf_token %}
to protect our form from cross-site scripting attacks.
In templates\auth folder, edit or create new HTML file named signup.html:
{% extends 'auth/base.html' %} {% block title %}Singup{% endblock title %} {% block body %} <div class="container"> <div class="row"> <div class="clo-12"> <div class="alert alert-info">this is Singup page</div> <a href="{% url 'home' %}" class="btn btn-primary mt-3">Home</a> <a href="{% url 'login' %}" class="btn btn-primary mt-3">Login</a> <br> <form method="post"> {% csrf_token %} {% for field in form %} <p> {{ field.label_tag }}<br> {{ field }} {% if field.help_text %} <small style="color: green">{{ field.help_text }}</small> {% endif %} {% for error in field.errors %} </p><p style="color: red">{{ error }}</p> {% endfor %} <p></p> {% endfor %} <button type="submit" class="btn btn-primary mt-3">Sign up </button> </form> </div> </div> </div> {% endblock body %}
it looks like:
• Create Django custom login.html
In templates\auth folder, edit or create new HTML file named login.html:
{% extends 'auth/base.html' %} {% block title %}Login{% endblock title %} {% block body %} <div class="container"> <div class="row"> <div class="clo-12"> <div class="alert alert-info">this is Login page</div> <a href="{% url 'home' %}" class="btn btn-primary mt-3">Home</a> <a href="{% url 'signup' %}" class="btn btn-primary mt-3">SignUp</a> <br><br><br> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit" class="btn btn-primary mt-3">Login</button> </form> </div> </div> </div> {% endblock body %}
We use {{ form.as_p }}
to render it within paragraph <p>
tags.
It looks like:
– Run Django project with command:
python manage.py runserver
– Open browser with url http://localhost:8000/
, then go to /signup
page and fill your information:
Click on Sign up Button, if the process is sucessful, the browser will turn into /home
page with your information:
Click on Log out and see the result:
Go to /login
page and fill Username and Password:
Click on Login to check authentication.
Now check MySQL database:
🙌 you can Download Source code 🙌
By Atik Gohel | April 7, 2021.