Django and Ajax: Robust authentication and authorization system with real-time form validations for web applications - 4
Tags: webdev, django, tutorial, javascript
Published: 2021-08-09 11:30:11

Last part, we incepted building the student registration system. We stopped at the point of creating some additional files, `tasks.py` and `tokens.py`. In this part, we'll continue with the implementation.

## Source code

The source code to this point is hosted on [github][1] while the source code for the entire application is:

{% github Sirneij/django_real_time_validation %}

## Step 7: `tokens.py` and `tasks.py` files

While concluding the part 3 of this series, we created `tokens.py` and `tasks.py` files. While the former handles creating unique tokens to validate users, the latter houses the logic for sending emails via `celery`. In this project, `celery`, a distributed task queue, handles all background tasks which encompass sending mails. With this, we will fulfill this segment of the requirements:

> ...Time attacks must be addressed by **sending the mails asynchronously**...

The content of `tokens.py` is pretty straightforward:

```python
# accounts > tokens.py

from django.contrib.auth.tokens import PasswordResetTokenGenerator

from six import text_type


class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, user, timestamp):
        return (
            text_type(user.pk)
            + text_type(timestamp)
            + text_type(user.is_student)
            + text_type(user.is_lecturer)
        )


account_activation_token = AccountActivationTokenGenerator()
```

Basically, we are inheriting django's `PasswordResetTokenGenerator` and then hashing based on the user's id (a UUID in our case), the time and other specific user attributes. It's fairly secure and unique! We then assign this to `account_activation_token` which we later called in our `student_signup` function.

To implement the `tasks.py`, we need to install [`celery`][2] with [`Redis`][3] backend. Ensure you have a full working setup for [redis][3].

In the virtual environment for this project, install using either `pip` or `pipenv`(if you have been using pipenv since inception) and set it up:

```bash
┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_real_time_validation]
└─$[sirneij@sirneij django_real_time_validation]$ pipenv install "celery[redis]"
```

Then, create a `celery.py` file in your project's directory. It should be in the directory as your project's `settings.py` file.

```bash
┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_real_time_validation]
└─$[sirneij@sirneij django_real_time_validation]$ touch authentication/celery.py
```

and populate it with:

```python
# authentication > celery.py
import os

from celery import Celery

# set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentication.settings")

app = Celery("authentication")

# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
#   should have a `CELERY_` prefix.
app.config_from_object("django.conf:settings", namespace="CELERY")

# Load task modules from all registered Django app configs.
app.autodiscover_tasks()


@app.task(bind=True)
def debug_task(self):
    print(f"Request: {self.request!r}")

```

This was copied from [using celery with django][4] with slight modifications of putting in my app's name in lines 6 and 8.

To ensure that the app is loaded when Django starts so that the @shared_task decorator will use it, import this app in your `project_name/__init__.py`:

```python
# authentication > __init__.py
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

__all__ = ("celery_app",)
```

Now to `tasks.py`:

```python
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core import mail
from django.template.loader import render_to_string
from django.utils.html import strip_tags

from celery import shared_task


@shared_task
def send_email_message(subject, template_name, user_id, ctx):
    html_message = render_to_string(template_name, ctx)
    plain_message = strip_tags(html_message)
    mail.send_mail(
        subject=subject,
        message=plain_message,
        from_email=settings.DEFAULT_FROM_EMAIL,
        recipient_list=[get_user_model().objects.get(id=user_id).email],
        fail_silently=False,
        html_message=html_message,
    )
```

It is a simple function decorated with celery's `shared_task`. It uses django's `mail` to send the messages. It is very important to ensure that you do not pass a user object into a celery task. Passing only one attribute of the user model, in this case `user_id`, is the solution. Passing model objects or instances leads to a common `Object not serializable` error. To wrap up the configurations, let's append to the `settings.py` this snippet:

```python
CELERY_BROKER_URL = config("REDIS_URL", default="")
CELERY_RESULT_BACKEND = config("REDIS_URL", default="")
CELERY_ACCEPT_CONTENT = ["application/json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
```

Your `REDIS_URL` is your local redis host and port(of the form `redis://host:port`). A good practice is to put this in a `.env` file and never upload it to `GitHub` by including the file path in your `.gitignore` file so as not to upload it for others to see.

## Step 8: Revisit and connect the student registration function to `urls.py`

Now that the preliminaries are taken care of, let examine the `student_signup` view function written in the last part. First, we initialized the `StudentRegistrationForm` and then checked that the request coming in is `POST`. If true, we made a copy of the request data and subsequently retrieved the `email`, `username` and `password` the request user inputted. If the `email` conforms with the rules created in the last part, a user instance is created and then, we test the user's `password` and `email` against other validations. If they scale through, we insert other user paramenters into the instance created and proceeded to sending the user a mail for confirmation. Take note of the context we passed into the celery task:

```python
...
ctx = {
    "fullname": user.get_full_name(),
    "domain": str(get_current_site(request)),
    "uid": urlsafe_base64_encode(force_bytes(user.pk)),
    "token": account_activation_token.make_token(user),
            }
```

Ensure you strigify the `get_current_site(request)`, if not you will run into a `celery` problem of not being able to serialize `request` data.

If user's password and username do not conform with our rules, such user is deleted from the database: `get_user_model().objects.get(email=post_data.get("email")).delete()`. Let's now add this to our `urls.py` file:

```python
# accounts > urls.py
...
urlpatterns = [
   ...
    path("student-sign-up/", views.student_signup, name="student_signup"),
]
```

We also need some functions to inform users that they need to check their email, and another to activate the user after clicking the link:

```python
# accounts > views.py
...
from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
...

def activate(request, uidb64, token):
    try:
        uid = force_text(urlsafe_base64_decode(uidb64))
        user = get_user_model().objects.get(pk=uid)
    except (TypeError, ValueError, OverflowError):
        user = None
    # checking if the user exists, if the token is valid.
    if user is not None and account_activation_token.check_token(user, token):
        # if valid set active true
        user.is_active = True
        user.save()
        messages.success(
            request, f"Your email has been verified successfully! You are now able to log in."
        )
        return redirect("accounts:login")
    else:
        return render(request, "accounts/activation_invalid.html")


def activation_sent_view(request):
    return render(request, "accounts/activation_sent.html")
```

The `activate` function uses the value from `uidb64` to get the user the token belongs to and then checks the token's validity before activating the user:

```python
# accounts > views.py
...
user.is_active = True
user.save()
...
```

Let's include them in our `urls.py` file:

```python
# accounts > urls.py
...
urlpatterns = [
   ...
    path("sent/", views.activation_sent_view, name="activation_sent"),
    path("activate/<uidb64>/<token>/", views.activate, name="activate"),
]
```

## Step 9: Creating login and other templates

To see what we have done so far, let's put in some html and css. Create `accounts/activation_sent.html`(mail sent notification template), `accounts/activation_invalid.html`(invalid token template), `accounts/student_signup.html`(student registration), `accounts/activation_request.txt`(for text-base emails) and `accounts/activation_request.html`(html-based email).

```bash
┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_real_time_validation]
└─$[sirneij@sirneij django_real_time_validation]$  touch templates/accounts/activation_sent.html templates/accounts/activation_invalid.html templates/accounts/student_signup.html templates/accounts/activation_request.txt templates/accounts/activation_request.html
```

`activation_request.txt` should look like:

```jinja
<!--templates/accounts/activation_request.txt-->

{% autoescape off %}
Hi {{ fullname }},
    Thank you for joining us on this great platform.
    Please click the following button to confirm your registration...


    By the way, if the above button is not clickable, paste the following link in your browser.
    http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}


Django Authentication Webmaster
{% endautoescape %}
```

Make `activation_request.html` appear as follows:

```jinja
<!--templates/accounts/activation_request.html-->

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width" />
    <style>
      * {
        margin: 0;
        padding: 0;
        font-size: 100%;
        font-family: "Avenir Next", "Helvetica Neue", "Helvetica", Helvetica,
          Arial, sans-serif;
        line-height: 1.65;
      }

      img {
        max-width: 100%;
        margin: 0 auto;
        display: block;
      }

      body,
      .body-wrap {
        width: 100% !important;
        height: 100%;
        background: #f8f8f8;
      }

      a {
        color: #206bc4;
        text-decoration: none;
      }

      a:hover {
        text-decoration: underline;
      }

      .text-center {
        text-align: center;
      }

      .text-right {
        text-align: right;
      }

      .text-left {
        text-align: left;
      }

      .button {
        display: inline-block;
        color: #ffffff;
        background: #206bc4;
        border: solid #206bc4;
        border-width: 10px 20px 8px;
        font-weight: bold;
        border-radius: 4px;
      }

      .button:hover {
        text-decoration: none;
        color: #ffffff;
        background-color: #1b59a3;
        border-color: #195398;
      }

      h1,
      h2,
      h3,
      h4,
      h5,
      h6 {
        margin-bottom: 20px;
        line-height: 1.25;
      }

      h1 {
        font-size: 32px;
      }

      h2 {
        font-size: 28px;
      }

      h3 {
        font-size: 24px;
      }

      h4 {
        font-size: 20px;
      }

      h5 {
        font-size: 16px;
      }

      p,
      ul,
      ol {
        font-size: 16px;
        font-weight: normal;
        margin-bottom: 20px;
      }

      .container {
        display: block !important;
        clear: both !important;
        margin: 0 auto !important;
        max-width: 580px !important;
      }

      .container table {
        width: 100% !important;
        border-collapse: collapse;
      }

      .container .masthead {
        margin-top: 20px;
        padding: 80px 0;
        background: #206bc4;
        color: #ffffff;
      }

      .container .masthead h1 {
        margin: 0 auto !important;
        max-width: 90%;
        text-transform: uppercase;
      }

      .container .content {
        background: #ffffff;
        padding: 30px 35px;
      }

      .container .content.footer {
        background: none;
      }

      .container .content.footer p {
        margin-bottom: 0;
        color: #888;
        text-align: center;
        font-size: 14px;
      }

      .container .content.footer a {
        color: #888;
        text-decoration: none;
        font-weight: bold;
      }

      .container .content.footer a:hover {
        text-decoration: underline;
      }
    </style>
    <title>Verify your email address.</title>
  </head>

  <body>
    <!-- auto -->
    {% autoescape off %}
    <table class="body-wrap">
      <tr>
        <td class="container">
          <!-- Message start -->
          <table>
            <tr>
              <td align="center" class="masthead">
                <h1>Welcome to Django Authentication System...</h1>
              </td>
            </tr>
            <tr>
              <td class="content">
                <h2>
                  Hi
                  <strong style="text-transform: capitalize"
                    >{{ fullname }}</strong
                  >,
                </h2>

                <p>Thank you for joining us on this great platform.</p>

                <p>
                  Please click the following button to confirm your
                  registration...
                </p>

                <table>
                  <tr>
                    <td align="center">
                      <p>
                        <a
                          href="http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}"
                          class="button"
                          >Yes, I'm in!</a
                        >
                      </p>
                    </td>
                  </tr>
                </table>

                <p>
                  By the way, if the above button is not clickable, paste the
                  following link in your browser.
                  <!-- email link -->
    http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}
                </p>

                <p><em>– Django Authentication Webmaster</em></p>
              </td>
            </tr>
          </table>
        </td>
      </tr>
      <tr>
        <td class="container">
          <!-- Message start -->
          <table>
            <tr>
              <td class="content footer" align="center">
                <p>
                  Sent by <a href="{{ domain }}">Django Authentication</a>,
                  Federal University of Technology, Akure, South Gate, Ondo
                  State, Nigeria.
                </p>
                <p>
                  <a href="mailto:nelsonidogun@gmail.com"
                    >nelsonidogun@gmail.com</a
                  >
                </p>
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    <!-- end auto -->
    {% endautoescape %}
  </body>
</html>

```

Just a simple html file. It incorporates some best practices for html mails.

`activation_sent.html` has this:

```jinja

<!--templates/accounts/activation_sent.html-->

{% extends 'base.html' %}
<!-- title -->
{% block title %} Verification email sent {% endblock title %}
<!-- static files -->
{% load static %}
<!-- content starts -->
{% block content %}

<div class="row center-content">
  <div class="col s12" style="max-width: 30rem">
    <div class="card blue-grey darken-1">
      <div class="card-content white-text">
        <span class="card-title">Thank you for creating an account!</span>
        <p>
          An email has been sent to the e-mail address you provided during
          registeration for confirmation.
        </p>
        <p>
          Make sure you visit the link provided in mail as it will soon be
          revoked.
        </p>
      </div>
    </div>
  </div>
</div>

<!-- content ends -->
{% endblock content %}

```

As for `activation_invalid.html`, it should be like this:

```jinja
{% extends 'base.html' %}
<!-- title -->
{% block title %} Verification email failed {% endblock title %}
<!-- static files -->
{% load static %}
<!-- content starts -->
{% block content %}

<div class="row center-content">
  <div class="col s12" style="max-width: 30rem">
    <div class="card blue-grey darken-1">
      <div class="card-content white-text">
        <span class="card-title">Invalid activation link!!</span>
        <p>
          Oops! There were issues with the activation link, it was highly
          perceived to have been used before... Please, consider requesting for
          an
          <a
            href="{% url 'accounts:resend_email' %}"
            class="btn waves-effect waves-light"
          >
            activate link resend </a
          >.
        </p>
      </div>
    </div>
  </div>
</div>
<!-- content ends -->
{% endblock content %}

```

Let's call it a day here. We'll continue from here next time!

## Outro
Happy birthday to me 🎂✨🥳🤩.

Enjoyed this article, consider [contacting me for a job, something worthwhile or buying a coffee ☕](mailto:sirneij@gmail.com). 

[1]: https://github.com/Sirneij/django_real_time_validation/tree/b17e9c91aba97a0b3c2d38dc192aab465489e3b2 "Third part code"
[2]: https://docs.celeryproject.org/en/master/userguide/periodic-tasks.html "Django celery"
[3]: https://redis.io/ "Redis"
[4]: https://docs.celeryproject.org/en/master/django/first-steps-with-django.html#django-first-steps "First steps with Django"