Requiring passwords to sign up for a service has many demerits, such as a high chance of passwords being stolen and requiring users to remember passwords all the time. On the contrary, a passwordless authentication system has many benefits. For instance, it saves users from being a victim of the most common attack—the Brute Force Attack. Additionally, many users have a tendency to use the same password for multiple websites/applications, which then can lead to a Credential Stuffing Attack. A passwordless authentication system helps save users from such an attack as well.
A passwordless authentication system lets users access the applications by verifying their identity using a secure token, biometric signature or any other secure proof of identity which is not knowledge based or does not require any private information.
In this tutorial, you will learn how to create a passwordless authentication system using Twilio Verify, SendGrid, Django, and Python. The proposed authentication system will require users to verify their identity using a one-time password.
Prerequisites
In order to follow along with this tutorial, you will need the following:
- A Twilio account and a SendGrid account
- Working knowledge of Python
- Basic knowledge of Django Model-View-Template (MVT) structure
Setting up the project
Let’s start by creating a virtual environment for our project. Navigate to where you would like to set up your project. Create a new directory for your project, and change into the directory.
If you are on Windows, run the commands below to create a virtual environment:
python -m venv myvenv
Navigate to the Scripts folder.
cd myvenv/Scripts
Start the virtual environment.
. ./activate
Then, navigate back to the main folder.
However, if you are working on Linux or macOS, run these commands instead.
python3 -m venv myvenv
source myvenv/bin/activate
Next, install Django and Twilio in the virtual environment.
pip install django twilio
Now, let's create a Django project named “myshop”.
django-admin startproject myshop
We will be creating a shopping app as part of this tutorial. Inside this shopping app, we will create different apps to manage different tasks as a part of this project “myshop”.
Navigate to the myshop folder.
cd myshop
Now, let's start by creating our first app which will manage:
- User registration
- User login
- User verification using a one-time password (OTP) every time user tries to log in
python manage.py startapp verification
Add the verification app in the "INSTALLED_APPS" field (inside myshop >> settings.py):
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'verification'
]
After this, we are all set to jump into the next steps and start the actual work!
Create a custom user model
For the purpose of creating a passwordless user authentication system, we need to create a custom user model. To do that navigate to: myshop >> verification >> models.py.
Create a NewUser
class in the models.py file which inherits from AbstractBaseUser
and PermissionsMixin
.
Note: Inheriting from AbstractBaseUser
provides the core implementation of a user model, and inheriting from PermissionsMixin
provides our model access to all the methods and database fields necessary to support Django’s permission model.
Add the code snippet below, which defines all the required fields:
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.core.validators import RegexValidator
class NewUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
username = models.CharField(max_length=150, unique=True)
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Please enter a valid phone number")
phone_number = models.CharField(validators=[phone_regex], max_length=17, blank=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
objects = NewUserAccountManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'phone_number']
def __str__(self):
return self.email
In the code snippet above, we:
- Set the
is_staff
andis_active
fields toFalse
by default. - Define the
USERNAME_FIELD
asemail
. (By doing so, we are setting email as a unique identifier) for our User model. - Define the
REQUIRED_FIELDS
, for example: username and phone number.
All objects for this class (NewUser
) come from the NewUserAccountManager
. As a next step, let’s create the NewUserAccountManager
class in the models.py file. This class will define all the methods required to create a user. It will inherit from BaseUserManager
.
This class will have two methods: create_user
and create_superuser
.
Although we do not need passwords for our regular users, we do need a password for our Django admin. So, for that we will check if the password is None
. If it is None
(while creating a user we will pass in the value as None
), then we will set the password as unusable password
.
In the case of the create_superuser
method, we will set certain fields as True
(is_staff
, is_superuser
, is_active
). And additionally, we will set a password for the admin/superuser.
Add the following code snippet to models.py, just above the code you added for the NewUser
class:
class NewUserAccountManager(BaseUserManager):
def create_superuser(self,username, email, phone_number,password, **other_fields):
other_fields.setdefault('is_staff', True)
other_fields.setdefault('is_superuser', True)
other_fields.setdefault('is_active', True)
if other_fields.get('is_staff') is not True:
raise ValueError('Superuser must be assigned to is_staff=True')
if other_fields.get('is_superuser') is not True:
raise ValueError('Superuser must be assigned to is_superuser=True')
user = self.create_user(username,email, phone_number, password, **other_fields)
user.set_password(password)
user.save()
return user
def create_user(self, username, email, phone_number,password,**other_fields):
if not email:
raise ValueError('Email address is required!')
email = self.normalize_email(email)
if password is not None:
user = self.model(username=username,email=email, phone_number=phone_number,password=password, **other_fields)
user.save()
else:
user = self.model(username=username,email=email, phone_number=phone_number, password=password,**other_fields)
user.set_unusable_password()
user.save()
return user
Finally, register the NewUser model inside the verification >> admin.py file.
from django.contrib import admin
from .models import NewUser
admin.site.register(NewUser)
Define the custom user model in settings.py
Add AUTH_USER_MODEL = 'verification.NewUser'
in the myshop >> settings.py file to register your newly created custom user model:
AUTH_USER_MODEL = 'verification.NewUser'
Now let's make and perform migrations. Run the following code on your command line:
python manage.py makemigrations
python manage.py migrate
Now let's try creating a superuser!
Run the command below:
python manage.py createsuperuser
Specify the username, email address, and password:
After you have created a superuser, run the Django server with the following command:
python manage.py runserver
By default, the server will run on port 8000. Navigate to http://127.0.0.1:8000/admin/ in your browser, and use your superuser credentials to log in to the Django admin. Once you log in, you will see this table:
If you click on "New users", you will see that a New users table has been created with an admin user.
Create Django forms
Now that we are done with user model creation, let's continue by creating our registration form.
Create a forms.py file in the verifications folder. Create a class called RegisterForm
and define all the required fields we need in the registration form.
Quick note about Django forms: Using Django forms makes it easy for us to do validation of all the fields that we need in the form.
According to the Django docs: A Django form instance has an is_valid()
method which validates all the fields. When we call this method and all the fields are validated correctly, then it returns True
and places all the form data in its cleaned_data
attribute.
Add the following code to forms.py:
from django import forms
from django.contrib.auth import get_user_model
User = get_user_model()
class RegisterForm(forms.Form):
username = forms.CharField(label="Username")
email = forms.EmailField(label="Email")
phone_number = forms.CharField(label="Enter your phone number here")
Although the input validation is already checked with the help of Django forms, let’s now create three methods inside our class to check if the user with the given email
, username
, or phone_number
already exists.
Update your RegisterForm
class with the following highlighted lines:
class RegisterForm(forms.Form):
username = forms.CharField(label="Username")
email = forms.EmailField(label="Email")
phone_number = forms.CharField(label="Enter your phone number here")
def clean_username(self):
username = self.cleaned_data.get('username')
user_details = User.objects.filter(username=username)
if user_details.exists():
raise forms.ValidationError("Username is already taken")
return username
def clean_email(self):
email = self.cleaned_data.get('email')
user_details = User.objects.filter(email=email)
if user_details.exists():
raise forms.ValidationError("There is already an account with this email , please try logging in!")
return email
def clean_phone_number(self):
phone_number = self.cleaned_data.get('phone_number')
user_details = User.objects.filter(phone_number=phone_number)
if user_details.exists():
raise forms.ValidationError("An account with this phone number already exists! Please try registering using a different phone number or try logging in!")
return phone_number
Similarly, let’s now create a login form. Create a class called LoginForm
just below your code for RegisterForm
, and define the required fields. (For the purpose of this tutorial, we only require one field for the login form — the email
field.)
Additionally, create one method to check if the account with the given email address exists or not:
class LoginForm(forms.Form):
email = forms.EmailField(label="Email")
def clean_email(self):
email = self.cleaned_data.get('email')
user_details = User.objects.filter(email=email)
if not user_details.exists():
raise forms.ValidationError("No account exists with this email. Try registering")
return email
Create registration view
Now head to the verification >> views.py file and create a registration view.
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, get_user_model
from .forms import RegisterForm
User = get_user_model()
def register_page(request):
form = RegisterForm(request.POST or None)
context = {
"form": form
}
if form.is_valid():
username = form.cleaned_data.get("username")
email = form.cleaned_data.get("email")
phone_number = form.cleaned_data.get("phone_number")
new_user = User.objects.create_user(username, email, phone_number, password=None)
return redirect("/login")
return render(request, "auth/register.html", context)
Here is what is happening in the code snippet above:
- In the
register_page
view, we create a form instance (of theRegisterForm
class, which we created above). - We check if the form is valid or not before processing the form data.
- If the form is valid, we process the form data.
- Finally, we call the
create_user
method and save the user in the database. - After that, we redirect the user to the login page. (We will create it afterwards.)
- Render the form using auth/register.html file (yet to create).
Before creating other views (login view, view to generate OTP and check OTP), we need to set up our SendGrid and Twilio accounts.
Setup Twilio and SendGrid accounts
- Go to the SendGrid website.
- For this tutorial, select a free plan, provide required details, and sign up!
After signing up, navigate to the Dashboard, and go to : Settings >> Sender Authentication.
Perform the sender authentication (single sender verification in this case) by verifying your email address. After verification, create a new sender by filling in the required details.
Now, go to Settings >> API Keys, and create an API key. Give your API key a name, and select "Full Access". After creating the API key, copy it and save it in a secure place because it will not be revealed to you again.
Now it's time to create an email template.
- Go to Email API >> Dynamic Templates.
- Click on “Create a Dynamic Template”.
- Give your template a name. (Example:
User-verification
) - After that, select your template name and click on “Add Version”.
- Choose a blank Email Template for now.
- Select “Code Editor”.
Change the code in the editor to the HTML below, and save:
<html>
<head>
<style type="text/css">
body, p, div {
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
}
a {
text-decoration: none;
}
</style>
<title></title>
</head>
<body>
<center>
<p>
The verification code is: <strong> {{twilio_code}} </strong>
</p>
<p>
<span style="font-size: 10px;"><a href=".">Email preferences</a></span>
</center>
</body>
</html>
In this code, we use the template variable: {{twilio_code}}
to send an OTP to the user.
Now, click on "Settings" on the left side of the template.
In the Settings panel, give the template version a name (Example: User Verification v1
or simply version-1
). Additionally, you can specify a suitable subject for the email. For instance: ‘Your One Time Password (OTP) for myshop’. You can also try sending test emails, by specifying more than one email address, such as: test1@gmail.com
, test2@gmail.com
, test3@gmail.com
.
After creating the template version, a template id will be created. (You will use this for the email integration later in the tutorial.)
Now create a Twilio account by visiting Twilio’s website.
- Create a Twilio Verify service by navigating to Verify >> Services, and clicking the "Create new" button.
- Give the service a friendly name (Example: ‘Passwordless-auth’), and enable Email as a verification channel.
- Create an email integration by navigating to Verify >> Email Integration in your Twilio account console and clicking the Create Email Integration button. Give your email integration a name, and click the Continue button.
- You will see a form where you will be required to add your SendGrid API key, template id (You can get your template id by logging in to the: SendGrid's website and navigating to Email API >> Dynamic Templates. Here you will find Template Id under your template name), from email (make sure it is the same as the email you verified in the SendGrid account), and from name. Enter these values and click the Save button.
- Navigate to Verify >> Services again, select the service you created, head to the Email tab, and enable the verification channel. Then, update the integration setting by selecting the email integration you just created.
- Finally, assign the service in the email integration.
Set up the environment file
Next, you need to define the environment variables for your project.
Install “python-dotenv” in the activated virtual environment.
pip install python-dotenv
Next, create an .env file in the myshop project folder (inside the main myshop folder: myshop >> myshop) and add the following variables:
SECRET_KEY = "add secret key here"
EMAIL_HOST = "smtp.sendgrid.net"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "apikey"
EMAIL_HOST_PASSWORD = "add sendgrid api key here"
DEFAULT_FROM_EMAIL = "add from email address here"
LOGIN_REDIRECT_URL = "/home"
ACCOUNT_SID = 'add account sid here'
AUTH_TOKEN = "add auth token here"
SERVICE_ID = 'add service id here'
TEMPLATE_ID = 'add template id here'
Add all the variables and their values in the .env file, including your secret key.
Instructions to get the values for .env file:
- The value of SECRET_KEY is present by default in the settings.py file of your project ( myshop >> myshop >> settings.py).
- Add the SendGrid API key, which you created in the
Setup Twilio and SendGrid accounts
section, as the value for: EMAIL_HOST_PASSWORD. - You can find values of ACCOUNT_SID and AUTH_TOKEN under the “Account Info” section by visiting Twilio’s console.
- To get the SERVICE_ID, visit Twilio console and navigate to Verify >> Services. You can now copy Service SID from the services table.
- Lastly, follow the instructions given in the section
Setup Twilio and SendGrid accounts
to get the value for TEMPLATE_ID.
Now, import os
in settings.py, and do the initialisation to read environment variables from the .env file:
from pathlib import Path
from dotenv import load_dotenv
import os
load_dotenv()
Replace the value of the secret key and other values in myshop >> settings.py with the format os.getenv('KEY_NAME')
, as shown below:
##email details
EMAIL_HOST = os.getenv('EMAIL_HOST')
EMAIL_PORT = os.getenv('EMAIL_PORT')
EMAIL_USE_TLS = os.getenv('EMAIL_TLS')
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL')
##other details
LOGIN_REDIRECT_URL = os.getenv('LOGIN_REDIRECT_URL')
ACCOUNT_SID = os.getenv('ACCOUNT_SID')
AUTH_TOKEN = os.getenv('AUTH_TOKEN')
SERVICE_ID = os.getenv('SERVICE_ID')
TEMPLATE_ID = os.getenv('TEMPLATE_ID')
Create a custom authentication backend
Since we want to authenticate users without using a password, we need to create a custom authentication backend.
Some useful points about authentication backend in Django: According to Django official docs, an authentication backend is a class which implements two required methods: get_user(user_id)
and authenticate(request, **credentials)
.
The authenticate()
method should check the credentials it gets and return a user object that matches those credentials if the credentials are valid. If they’re not valid, it should return None
.
To do this, create a Python file called auth_backend.py in the verification directory. In that file, create two methods: get_user()
and authenticate()
, as required:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
class PasswordlessAuthBackend(ModelBackend):
"""Log in to Django without providing a password.
"""
def authenticate(self, request, email):
User = get_user_model()
try:
user = User.objects.get(email=email)
return user
except User.DoesNotExist:
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
By default Django uses django.contrib.auth.backends.ModelBackend
as a backend. Since we created a custom authentication backend, we need to add it to our settings.py.
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'verification.auth_backend.PasswordlessAuthBackend',
)
Now, we are all set to create the remaining views!
Create login view
To create a login view, we first need to create a send_code()
function which will send an OTP to the user.
- Create a file called verify.py in the verification directory.
- Import
Client
from twilio.rest, and import all required environment variables. - Add the SendOTP class with the
send_code()
method.
from twilio.rest import Client
from django.conf import settings
class SendOTP:
def send_code(receiver):
account_sid = settings.ACCOUNT_SID
auth_token = settings.AUTH_TOKEN
client = Client(account_sid, auth_token)
verification = client.verify \
.services(settings.SERVICE_ID) \
.verifications \
.create(channel_configuration={
'template_id': settings.TEMPLATE_ID,
'from': settings.DEFAULT_FROM_EMAIL,
'from_name': 'Ashi Garg'
}, to=receiver, channel='email')
return verification.status
Now, in verification >> views.py, import uuid
, the LoginForm
that you created, and the SendOTP
class from the verify.py file:
import uuid
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, get_user_model
from django.contrib import messages
from .forms import RegisterForm, LoginForm
from .verify import SendOTP
Just below the register_page() view, add the following login_page() view:
def login_page(request):
form = LoginForm(request.POST or None)
context = {
"form": form
}
if form.is_valid():
email = form.cleaned_data.get('email')
try:
new = User.objects.get(email=email)
## if user exists
##first: send otp to the user
SendOTP.send_code(email)
##second:redirect to the page to enter otp
temp = uuid.uuid4()
return redirect("/otp/{}/{}".format(new.pk, temp))
except Exception as e:
messages.error(request, "No such user exists!")
return render(request, "auth/login.html", context)
After the OTP is sent to the user, we now need to verify the OTP.
For that we need two views: one which will display the OTP form, and another which will check the OTP.
Create generate_otp view
def generate_otp(request, pk, uuid):
return render(request, 'otp.html')
This view will just render the OTP form when the user is redirected to the url: /otp/pk/uuid
after logging in, where pk
is the primary key of the user and uuid
is the temporary generated uuid.
Create check_otp view
Now, to check the OTP, we need to create a CheckOTP
class and check_otp()
function, which will verify the OTP given the email address and OTP.
Create a check_code.py file inside verification directory and add the following code:
import os
from twilio.rest import Client
from django.conf import settings
# Find your Account SID and Auth Token at twilio.com/console
# and set the environment variables. See http://twil.io/secure
class CheckOTP:
def check_otp(email, secret):
account_sid = settings.ACCOUNT_SID
auth_token = settings.AUTH_TOKEN
client = Client(account_sid, auth_token)
verification_check = client.verify \
.services(settings.SERVICE_ID)\
.verification_checks \
.create(to=email, code=secret)
return verification_check.status
Now let's create our check_otp
view in views.py which will:
- Get the email address and OTP entered by the user.
- Pass those values to our check OTP function.
- If the OTP is correct, it will authenticate the user and redirect the user to our home page.
At the top of views.py, add the CheckOTP
class to your list of imports:
from .check_code import CheckOTP
Then, below your other views, add a view for check_otp
:
def check_otp(request):
otp =request.POST.get("secret")
email = request.POST.get("email")
otp_status= CheckOTP.check_otp(email, otp)
if otp_status == "approved":
user = authenticate(request, email=email)
if user is not None:
login(request, user, backend='verification.auth_backend.PasswordlessAuthBackend')
return redirect("/home")
else:
messages.error(request, "Wrong OTP!")
print("otp via form: {}".format(otp))
return render(request, "otp.html")
Note that in the login method, we are specifically defining the authentication backend we want to use as verification.auth_backend.PasswordlessAuthBackend
.
Create home view
Now, the last view that we need to create is the home view. It will just render the home_page.html template and show premium content if the user is authenticated — otherwise it will display a simple message: "Please login to continue".
def home_page(request):
return render(request, "home_page.html")
Connect URLs to their respective views
In myshop >> urls.py, we will:
- Import all the required views that you created.
- Define the paths associated with all the views.
Add the following code below to urls.py:
from django.contrib import admin
from django.urls import path
from verification.views import register_page, home_page, login_page, generate_otp, check_otp
urlpatterns = [
path('admin/', admin.site.urls),
path('register/', register_page, name="register"),
path('check/', check_otp, name="check_otp"),
path('login/', login_page, name="login"),
path('otp/<int:pk>/<uuid>/', generate_otp),
path('home/', home_page)
]
Create templates
Before creating a templates folder, we need to define it in our settings.py file, so that Django can locate it.
Go to myshop >> settings.py, and add templates
inside the DIRS
field:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Now create a templates folder in the main project folder:
Further, inside the templates folder, create an auth folder where you will add two new files: register.html and login.html.
Create register.html
This will be just a basic Django form with the fields we defined in our forms.py file. Copy the following code into auth/register.html.
<!DOCTYPE html>
<html>
<head>
</head>
<body style="background-color:cadetblue;">
<h1 style="text-align:center;"> Register yourself for an awesome shopping experience !</h1>
<br>
<form method="POST" style="text-align:center;"> {% csrf_token %}
<div class="container" style="background-color: grey; height: 400px; width:600px; text-align:center; border: 5px solid black; margin: 0 auto;">
</br>
</br>
</br>
{{ form.as_p }}
<button type="submit" style="text-align:center;">Submit</button>
<p><b>Already have an account? </b><a href="{% url 'login' %}">Sign in</a>.</p>
</div>
</body>
</html>
Note that we have used the {{ form.as_p }}
method to render the form as a paragraph. You can also use {{ form.as_table }}
or {{ form.as_ul }}
depending upon the use case.
Create login.html
Just like auth/register.html, auth/login.html will also display a basic Django form with the fields defined in forms.py. The only difference is that here we will display a message if there is an error in the value entered by the user in the form.
Copy the following code into auth/login.html:
<!DOCTYPE html>
<html>
<head>
</head>
<body style="background-color:khaki;">
{% if messages %}
{% for message in messages %}
{% if message.tags == "error" %}
<div class="alert alert-danger" >
<h1 style="border: 5px red;"> {{ message }} </h1>
</div>
{% else %}
<div class="alert alert-{{ message.tags }}">
<h1 style="border: 5px red;"> {{ message }} </h1>
</div>
{% endif %}
{% endfor %}
{% endif %}
<h1 style="text-align:center;"> Welcome Back!!</h1>
<h2 style="text-align: center;"> Login to continue</h2>
<div class="container" style="background-color: grey; height: 400px; width:600px; text-align:center; border: 5px solid black; margin: 0 auto;">
</br>
</br>
<form method="POST" style="text-align:center;"> {% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-default" style="text-align:center; background-color:white;">Submit</button>
<p><b> Create an account? </b><a href="{% url 'register' %}">Register here</a>.</p>
</div>
</form>
</body>
</html>
Create otp.html
Next, create a file called otp.html inside the templates folder. Note that this file is outside of the auth folder.
This page will have 2 fields — email address and OTP. After the user submits the form, the user is redirected to the “/check” url.
Add the following code to otp.html:
<!DOCTYPE html>
<html>
<body style="background-color:khaki;" >
<div class="container" style="background-color: grey; height: 400px; width:600px; text-align:center; border: 5px solid black; margin: 0 auto;">
<h2> We have sent an OTP to your registered email address. Please confirm the OTP to continue.</h2>
<form method="POST" action=" {% url 'check_otp' %} " style="text-align:center;"> {% csrf_token %}
{% if messages %}
{% for message in messages %}
{% if message.tags == "error" %}
<div class="alert alert-danger">
{{ message }}
</div>
{% else %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endif %}
{% endfor %}
{% endif %}
<h2> <strong> Enter email address</strong></h2>
<input type="email" name="email" required>
<h2><strong> Enter OTP</strong></h2>
<input type="text" name="secret" inputmode="numeric" autocomplete="one-time-code" pattern="\d{6}" required>
</br>
</br>
<button type="submit" class="btn btn-default" style="text-align:center; background-color:white;">Submit</button>
</form>
</div>
</body>
</html>
Create home_page.html
Next, create another file in the templates folder called home_page.html.
Here, we will check if the user is authenticated. If the user is authenticated, then we will show them the premium content. Otherwise we will simply display a message: "Please login to continue.
Add the following code to home_page.html:
<!DOCTYPE html>
<html>
<head>
</head>
<body style="background-color: grey;">
{% if request.user.is_authenticated %}
<div class="container">
<div class="row" style="position: relative; left:300px;">
<div class="col">
<h1 style="text-align:center;margin: 0 auto; "> Welcome to the online shop! ! </h1>
<img src='https://static.vecteezy.com/system/resources/previews/000/172/891/original/online-shopping-center-free-vector.jpg' style=" text-align: centre; display:block; margin-left: auto; margin-right: auto; border: 5px black; height: 600px; width: 800px;"/>
</div>
</div>
</div>
{%else %}
<div class="container" style="position: relative; left:300px;">
<h1>Please login, to continue</h1>
</div>
{% endif %}
</body>
</html>
Testing
Now, let’s test our authentication system!
To restart your application, navigate to the folder which contains the manage.py file, and run the following command:
python manage.py runserver
Access the registration page at http://127.0.0.1:8000/register/, and register a user.
Note: For the purpose of this tutorial, the template is kept basic, but you can make changes in the UI as required.
Now if everything works well, you will be redirected to the login page:
After you click on the Submit button, you will be redirected to the page where you need to enter the OTP you received at your registered email address.
You will receive an email like this:
After submitting the correct email address and OTP, you will be redirected to the home page.
If you are not authenticated, a simple message will be displayed like this:
Please login, to continue.
Conclusion
Congratulations! You have successfully created a passwordless authentication system. The source code for the passwordless authentication system can be found here: Passwordless authentication system.
What’s next?
- Add the logout view
- Add features in the home page
- Improve upon the design of the template
Ashi Garg is a Computer Science Engineer. Her interests include developing systems that are safe and easy to use and keeping herself abreast with new technological trends. She can be reached via email and LinkedIn.