Authentication in Django using Python Decorators
In this article, we are going to talk a bit about Python Decorators, its use cases, advantages, and also we will see how we can use them for authentication in Django.
Prerequisite
To follow up on this tutorial, you should be familiar with Python and Django framework.
Note
All the demonstration codes were tested with Python 3.8.10 and Django 3.2 but it should work with other versions.
What is a Python Decorator
A Python Decorator is a function or class that takes in another function and modifies or adds extra functionality to the function.
Let’s look at some examples to properly understand what python Decorators are
Technical examples of Decorators
In python you can pass a function as an argument to another function. Let’s see how we can use that to create a Decorator that adds extra functionality to a function.
def decorator_function(func):
print("adding extra functionality")
return func()
def decorated_function():
print("decorated function")
decorated_function = decorator_function(decorated_function)
#OUTPUT
# adding extra functionality
# decorated function
In this example we took the decorated_function
as argument to our decorated_function
, then we added extra functionality before returning our decorated_function
. Notice we Instantiated our decorator_function
with the decorated_function
then assigned it to a variable called decorated_function
. This is how you define can define Decorators, however this isn’t the typical Python way of defining Decorators.
Let’s see an example of how Python prefers Decorators to be defined.
def decorator_function(func):
print("adding extra functionality")
return func()
@decorator_function
def decorated_function():
print("decorated function")
#OUTPUT
# adding extra functionality
# decorated function
The above codes won’t suffice when working with arguments in our decorated_function. To work with arguments, you have to modify the code to look like this:
def decorator_function(func):
print("adding extra functionality")
def wrapper(arg):
return func(arg)
return wrapper
@decorator_function
def decorated_function(name):
print("decorated function")
# You call the function like this:
decorated_function("tammy")
but in this code we are not making use of the argument. Below is an example of how you can use the argument.
def decorator_function(func):
def wrapper(arg):
upper = arg.upper()
return func(upper)
return wrapper
@decorator_function
def decorated_function(name):
print("your can call me", name)
decorated_function("tammy")
#OUTPUT
#TAMMY
In the above code we called the decorated_function with the argument “tammy”, then we modified or changed it to uppercase before printing it.
This is an example of how decorators can be used to modify a function. You can read more on decorators here:
https://book.pythontips.com/en/latest/decorators.html.
Let’s look at some advantages of python decorators
Advantages of Python Decorator
- It extends the functionality of functions without modifying the internal code of the functions.
- You can use any number of Decorators in on a function in any order.
Now that we know about Python Decorators, let’s see how they can be used for authentication in Django by building an authentication system.
Building an authentication system
In this section we are going to build a login system that will be used to authenticate user credentials, using a Python decorator to add the authentication logic. But first let us start by setting up a new Django project.
Setting up Django
- Install Django.
- Create a new project.
- Create an app inside the project.
- Migrate your project.
- Create a url.py file in your app.
- Create a forms.py file in your app.
You can accomplish all this with the following commands.
$ python -m pip install django==3.2
$ django-admin startproject authDecorator
$ cd authDecorator
$ python manage.py startapp authApp
$ python manage.py migrate
$ cd authApp
$ type nul > urls.py
$ type nul > forms.py
You now have an authDecorator project and authApp app setup. Now you need to tell Django about the app you created and the location of the app url.py file. You can do this by modifying the authDecorator/settings.py file. First, we will add the app by modifying the INSTALLED_APPS:
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"authApp",
]
Once you have added it, go over to the urls.py **file in the same directory and modify the *urlpatterns* by including your app urls.
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('authApp.urls'))
]
Now that we have done that, let’s start building the login system to be used for user authentication. We will first create a login form then later on we will add the authentication logic.
Creating a registration view
In this section we will create a simple view to allow user registration. Initially, we will create a form to allow the user to enter their username and password, then later on we will build the logic for the form.
Creating a registration form
We are going to user the UserCreationForm provided to us by Django. This form handles the creation of a new user. It has three default fields namely username, password1
and password2
. To use this form you have to first import it from django.contrib.auth.forms like this:
from django.contrib.auth.forms import UserCreationForm
Unfortunately, Django doesn’t provide any logic to handle this form, so we have to manually create the logic.
Creating registration logic
Edit the views.py file of the authApp application and add the following code to it:
authApp/views.py
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect
def register_view(request):
if request.method == 'POST':
register_form = UserCreationForm(request.POST)
if register_form.is_valid():
register_form.save()
return redirect('login')
else:
register_form = UserCreationForm()
return render(request, 'register.html', {'register_form': register_form})
this is what our register_view does: when the register_view
is called with a GET request, we instantiate a new registration form with register_form = UserCreationForm()
to be displayed in the template. When the user submits the form via POST we do the following:
- Instantiate the form with the submitted data with
register_form = UserCreationForm(request.POST)
. - Check if the form is valid. If it is not valid, the form will be re-rendered.
- Once the form is valid we will save the form and redirect to the login page.
Now that we have created the logic for the registration view, we need to add the URL pattern for this view. In the urls.py file of the authApp application, add the following code:
from django.urls import path
from . import views
urlpatterns = [
path('register/', views.register_view, name='register'),
]
The registration view can now be accessed by a URL. It is time to create a template for this view. Create a templates directory and a register.html file in the templates directory. You can do this with the following command:authDecorator/authApp
$ mkdir templates
$ cd templates
$ type nul > register.html
Now, let’s create a template for the registration view. Open the register.html file and add the following lines of code:
authApp/templates/register.html
<!Doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register</title>
</head>
<body>
<div class="wrapper">
<form method="POST">
{{register_form.as_p}}
<button type="submit">Register</button>
{% csrf_token %}
</form>
</div>
</body>
</html>
This template includes the form that is instantiated in the view. Since our form will be submitted via POST, we included the {% csrf_token %} template tag for CSRF protection.
Now run the development server and open http://127.0.0.1:8000/register/
in your browser. You will see the registration page we created, it will look something like this:
Creating a login view
In this section we will use the Django authentication framework to allow users to log in to our website. Instead of using the conventional way of handling both authentication and login in one function, we will be using Python Decorator to handle the authentication then handle the login in our view function. But first we will start by creating a login form.
Creating a login form
Open forms.py or the authApp directory and add the following lines of code:
authApp/forms.py
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(label='')
password = forms.CharField(label='',widget=forms.PasswordInput)
def __init__(self,*args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].widget.attrs.update({'placeholder':'Username'})
self.fields['password'].widget.attrs.update({'placeholder':'Password'})
what we did here was this:
- We created a username and password field.
- We set the labels for both fields to an empty string.
- We gave them placeholder values related to their field names.
Authentication with Python Decorators**
In the authApp, create a decorator.py file with this command:
authDecorator/authApp
$ type nul > decorator.py
Open the decorator.py file and add the following code:authApp/decorator.py
from django.contrib.auth import authenticate
from django.shortcuts import render
from .forms import LoginForm
from django.http import HttpResponse
from django.contrib import messages
def authenticate_user(login_func):
def wrapper(request, user=None):
if request.user.is_authenticated:
return HttpResponse('You have been Authenticated')
if request.method == 'POST':
login_form = LoginForm(request.POST)
if login_form.is_valid():
cd = login_form.cleaned_data
user = authenticate(username=cd['username'], password=cd['password'])
if user is not None:
if user.is_active:
return login_func(request,user)
else:
messages.error(request, 'User is disabled')
else:
messages.error(request, 'User does not exit')
else:
messages.error(request, 'Invalid username of passoword')
else:
login_form = LoginForm()
return render(request, 'login.html', {'login_form': login_form})
return wrapper
This is what our Decorator does: when our Decorator function is called with a GET request, we instantiate a new log in with login_form = LoginForm() to display it in the template. When the user submits the form via POST, we perform the following actions:
- Instantiate the form with the submitted data with
login_form = LoginForm(request.POST)
. - Check if the form is valid. If it is not valid, we will display the form error in our template using Django’s message framework.
- If the submitted data is valid, we authenticate the user against the database by using the
authenticate()
method. - If the user has not been authenticated, we will return an error message.
- If the user was successfully authenticated, we check if the user is active by accessing its
is_active
attribute. This is an attribute of Django’s User model. If the user is not active, it will return an error message. - If the user is active, we return
login_func
, this is the view function that is responsible for user login. We Instantiated the function with the request and user object, they are required for the login process.
Now that we have defined our Decorator lets create a login view.
In the authApp directory, open view.py and add the following lines of code:authApp/views.py
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.http import HttpResponse
from .decorator import authenticate_user
def register_view(request):
#...
@authenticate_user
def login_view(request, user=None):
login(request, user)
return HttpResponse('{} you have logged in successfully'.format(request.user))
What this view does is to login a user then return a HttpResponse telling the user that the login was successful.
Notice how we placed @authenticate_user before our function, this is how we decorate our login_view with our already created Decorator function.
Now let’s add the URL for the views. In authApp/url.py
add the following lines of code:authApp/url.py
from django.urls import path
from . import views
urlpatterns = [
#...
path('login/', views.login_view, name='login'),
]
The login view can now be accessed by a URL
Now, let’s create a template for the login view. Navigate to authApp/templates and create a login.html file. You can do this with the command
authApp/templates
$ type nul > login.html
Now, pen the login.html file and add the following lines of code:authApp/templates/login.html
<!Doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<div class="wrapper">
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<form action="{% url 'login' %}" method="POST">
{{login_form.as_p}}
<button type="submit">Login</button>
{% csrf_token %}
</form>
</div>
</body>
</html>
what we did here was this:
- We checked if there were messages attached to the request, if there was, we iterate through all the messages and displayed them in a list.
- We created a form that sends a post request to our view when it is submitted.
- We rendered the form which was instantiated in the login view.
- We also included the {% csrf_token %} template tag for CSRF protection
With this, we are done building the authentication app. Run the development server and open http://127.0.0.1:8000/login/
in your browser. You will see the login page we created, it will look something like this:
Testing our app
You can test the app by creating a new user on the registration page, then login with the user credentials on the login page.
Conclusion
It’s been quite a journey. We started by looking at Python Decorators, what they are, how they can be used and also some advantages, then looked at how they can be used in Django to handle authentication. you can find source code on Github.