FullStack JWT Authentication and Authorization System with Django and SvelteKit
Tags: webdev, svelte, django, tutorial
Published: 2022-02-07 11:47:38

## Introduction

This is the first of a series of articles that will give a work-through of how to build a secure, robust, and reliable Authentication and Authorization system using modern web technologies viz: [Django][1], [Django REST Framework][12], [JWT][10], and [SvelteKit][7]. It also demonstrates the new paradigm called `#transitionalapps`, a fusion of `#SPA` and `#MPA`, arguably propounded by @richharris in this [talk][19].

## Motivation

A while ago, I built a data-intensive application that collects, analyzes, and visualizes data using [Django][1], [Plotly][2], and [Django templating language][3]. However, an upgrade was recently requested which made me tend to re-develop the application from the ground up. A pivotal aspect of the app is Authentication and Authorization system since the data are confidential and only authorized personnel should be allowed access. I thought of making the architecture strictly client-server while maintaining [Django][1] at the backend. The major battle I had was choosing a suitable JavaScript frontend framework/library. I had had some upleasant attempts in learning [React][4] in the past but a fairly pleasant one with [Vue][5]. I thought of [Svelte][6] and/or it's "extension", [SvelteKit][7] with SSR. I had no experience working with it so I decided to learn it. This [Dev Ed's youtube tutorial][8] sold me all out! I decided to write about my experiences and challenges along the way and how I edged them since resources on [SvelteKit][7] are relatively scarce compared to [React][4], [Vue][5], and [Angular][9] but surprisingly faster without compromising SEO. On the backend, I was tired of using cookies and storing them in the browser to track users so I opted for [JSON Web Tokens (JWT)][10]. Though I initially wrote the [JWT][10] authentication backend from scratch, I eventually settled for [Django REST Framework Simple JWT][11].

## Tech Stack

As briefly pointed out in the introduction, we'll be using:

- Python(Version==3.9),[Django][1](Version == 4.0.2), [Django REST Framework][12](Version==3.13.1), and [REST Framework Simple JWT][11](Version==5.0.0) at the Backend;
- [SvelteKit][7] at the frontend.

## Source code

The overall source code for this project can be accessed here:

{% github  Sirneij/django_svelte_jwt_auth  %}

## Live version

This project was deployed on heroku (backend) and vercel (frontend) and its live version can be accessed [here](https://django-sveltekit-jwt-auth.vercel.app/).

## Assumption

It is assumed you are familiar with Python 3.9 and its type checking features, and [Django][1]. Also, you should know the basics of TypeScript as we'll be using that with [SvelteKit][7].

## Initial project setup

Currently, the structure of the project is as follows (exluding the node_modules folder):

```
├── backend
│   ├── backend
│   │   ├── asgi.py
│   │   ├── __init__.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── manage.py
├── frontend
│   ├── package.json
│   ├── package-lock.json
│   ├── README.md
│   ├── src
│   │   ├── app.d.ts
│   │   ├── app.html
│   │   └── routes
│   │       └── index.svelte
│   ├── static
│   │   └── favicon.png
│   ├── svelte.config.js
│   └── tsconfig.json
├── Pipfile
└── Pipfile.lock
```

This intitial setup can be grabed [here][14]. If you would like to rather incept from the groundup, create a folder with your preferred name. In my case, I chose `django_svelte_jwt_auth`. Change your directory into it. Fire up a virtual environment, install the dependencies, and also initialize the sveltekit app. The processes are summarized in the commands below:

```shell
sirneij@pop-os ~/D/P/Tutorials> mkdir django_svelte_jwt_auth && cd django_svelte_jwt_auth  #create directory and change directory into it

sirneij@pop-os ~/D/P/T/django_svelte_jwt_auth> pipenv shell #fire up virtual environment

(django_svelte_jwt_auth) sirneij@pop-os ~/D/P/T/django_svelte_jwt_auth> pipenv install django djangorestframework djangorestframework-simplejwt gunicorn whitenoise psycopg2-binary  #install the dependencies

(django_svelte_jwt_auth) sirneij@pop-os ~/D/P/T/django_svelte_jwt_auth> django-admin startproject backend  #start django project with the name backend

sirneij@pop-os ~/D/P/T/django_svelte_jwt_auth> npm init svelte@next frontend   #start a sveltekit project, I chose skeleton project, activated typescript support, and allowed linters

sirneij@pop-os ~/D/P/T/django_svelte_jwt_auth> cd frontend && npm i  #change directory to frontend and installed dependencies.
```

**If you cloned the project setup on github, ensure you install all the dependencies required.**

## Section 1: Build the backend and create APIs

Now, let's get to the real deal. We'll be building authentication and authorization API services for the frontend (we'll come back to this later) to consume. To start out, we will create an `accounts` application in our django project:

```shell
(django_svelte_jwt_auth) sirneij@pop-os ~/D/P/T/d/backend (main)> python manage.py startapp accounts
```

The add the newly created app to our project's `settings.py`:

```python
INSTALLED_APPS = [
    ...
    # local apps
    'accounts.apps.AccountsConfig', #add this
]
```

Proceeding to our application's `models.py`, we will be subclassing django's `AbstractBaseUser` to create our custom `User` model. This is to allow us have full control of the model by overriding the model shipped by django. It is a recommended practice officially. For references, you are persuaded to checkout [Customizing authentication in Django][15], [How to Extend Django User Model][17] and [Creating a Custom User Model in Django][16]. To achieve this, open up `accounts/models.py` and populate it with:

```python
# backend -> accounts -> models.py

import uuid
from typing import Any, Optional

from django.contrib.auth.models import (
    AbstractBaseUser,
    BaseUserManager,
    PermissionsMixin,
)
from django.db import models
from rest_framework_simplejwt.tokens import RefreshToken


class UserManager(BaseUserManager):  # type: ignore
    """UserManager class."""

    # type: ignore
    def create_user(self, username: str, email: str, password: Optional[str] = None) -> 'User':
        """Create and return a `User` with an email, username and password."""
        if username is None:
            raise TypeError('Users must have a username.')

        if email is None:
            raise TypeError('Users must have an email address.')

        user = self.model(username=username, email=self.normalize_email(email))
        user.set_password(password)
        user.save()

        return user

    def create_superuser(self, username: str, email: str, password: str) -> 'User':  # type: ignore
        """Create and return a `User` with superuser (admin) permissions."""
        if password is None:
            raise TypeError('Superusers must have a password.')

        user = self.create_user(username, email, password)
        user.is_superuser = True
        user.is_staff = True
        user.is_active = True
        user.save()

        return user


class User(AbstractBaseUser, PermissionsMixin):

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    username = models.CharField(db_index=True, max_length=255, unique=True)
    email = models.EmailField(db_index=True, unique=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    bio = models.TextField(null=True)
    full_name = models.CharField(max_length=20000, null=True)
    birth_date = models.DateField(null=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

    # Tells Django that the UserManager class defined above should manage
    # objects of this type.
    objects = UserManager()

    def __str__(self) -> str:
        """Return a string representation of this `User`."""
        string = self.email if self.email != '' else self.get_full_name()
        return f'{self.id} {string}'

    @property
    def tokens(self) -> dict[str, str]:
        """Allow us to get a user's token by calling `user.token`."""
        refresh = RefreshToken.for_user(self)
        return {'refresh': str(refresh), 'access': str(refresh.access_token)}

    def get_full_name(self) -> Optional[str]:
        """Return the full name of the user."""
        return self.full_name

    def get_short_name(self) -> str:
        """Return user username."""
        return self.username

```

It's a simple model with all recommended methods properly defined. We just enforce `username` and `email` fields. We also ensure that `email` will be used in place of `username` for authentication. A prevalent paradigm in recent times. As suggested, you can lookup the details of using this approach in the suggested articles. We also ensure that each user has `bio` and `birthdate`. Other fields are basically for legacy purposes. A very important method is the `tokens` property. It uses `RefreshToken` from [Simple JWT][11] to create a set of tokens to recognize a user. The first being `refresh` token which tends to "live" relatively longer than its counterpart `access`. The former will be saved to user's browser's localStorage later on to help recreate `access` token since the latter is the only token that can authenticate a user but has very short live span. [Simple JWT][11], having been set as our default REST Framework's Default authentication class in our `settings.py`:

```python
# backend -> backend -> settings.py
...
# REST FRAMEWORK
REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework_simplejwt.authentication.JWTAuthentication',)}

```

knows how to verify and filter the tokens making requests.

It'll be observed that `Python's types` were heavily used with the help of [`mypy`][18], a library for static type checking in python. It is not required but I prefer types with python.

Next, we'll make django aware of our custom `User` model by appending the following to our `settings.py` file:

```python
# backend -> backend -> settings.py

...
# DEFAULT USER MODEL
AUTH_USER_MODEL = 'accounts.User'
```

Now, its safe to run migrations:

```shell
(django_svelte_jwt_auth) sirneij@pop-os ~/D/P/T/d/backend (main)> python manage.py makemigrations
```

If everything goes well, you should see:

```shell
Migrations for 'accounts':
  accounts/migrations/0001_initial.py
    - Create model User
```

Then, migrate to 'really create the database.

```shell
(django_svelte_jwt_auth) sirneij@pop-os ~/D/P/T/d/backend (main)> python manage.py migrate
```

That's it for this part. Up next is creating the serializers that will be used by our views and endpoints. Stick around please...

## Outro

Enjoyed this article, consider [contacting me for a job, something worthwhile or buying a coffee ☕](mailto:sirneij@gmail.com). You can also connect with/follow me on [LinkedIn](https://www.linkedin.com/in/idogun-john-nelson/).

[1]: https://www.djangoproject.com/ "Django Web Framework."
[2]: https://plotly.com/ "Plotly: The frontend for ML and Datascience."
[3]: https://docs.djangoproject.com/en/4.0/ref/templates/language/ "The Django Template Language."
[4]: https://reactjs.org/ "React.js"
[5]: https://vuejs.org/ "The Progressive JavaScript Framework."
[6]: https://svelte.dev/ "Cybernetically enhanced web apps."
[7]: https://kit.svelte.dev/ "The fastest way to build svelte apps."
[8]: https://www.youtube.com/watch?v=ydR_M0fw9Xc "SvelteKit For Beginners | Movie App Tutorial."
[9]: https://angular.io/ "The modern web developer's platform."
[10]: https://jwt.io/ "JSON Web Tokens."
[11]: https://django-rest-framework-simplejwt.readthedocs.io/en/latest/ "Simple JWT."
[12]: https://www.django-rest-framework.org/ "Django REST Framework."
[13]: https://tailwindcss.com/ "Tailwind CSS."
[14]: https://github.com/Sirneij/django_svelte_jwt_auth/commit/5b496542a2e86bf3c5672d55e1e7c09198e14b05 "Initial setup"
[15]: https://docs.djangoproject.com/en/4.0/topics/auth/customizing/ "Customizing authentication in Django"
[16]: https://testdriven.io/blog/django-custom-user-model/ "Creating a Custom User Model in Django"
[17]: https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html "How to Extend Django User Model"
[18]: https://mypy.readthedocs.io/en/stable/ "mypy"
[19]: https://www.youtube.com/watch?v=860d8usGC0o "Have Single-Page Apps Ruined the Web? | Transitional Apps with Rich Harris, NYTimes"