Exploring JWT Authentication in Django : Part 1 - Understanding the Basics
Posted on: 25 April 2024 | Last updated: 25 April 2024 | Read: 9 min read | In: Django, Python
Description
Full blog
Exploring JWT Authentication in Django : Part 1 - Understanding the Basics
Hello World 👋, I am Dakshesh Jain, and in this blog series, we will explore JWT Authentication in Django. In this first part, we will focus on setting up the project, understanding the basics, and changing the User
model.
1. Setting up the project
For purpose of this blog series, we will keep the project setup nice and simple. I won't use cookie-cutters, poetry, or any other fancy tools. Although, I highly recommend looking into them if you are building a production application.
1.1. Setting up the environment
It's a good practice to have a separate environment for each Python project to ensure that any update to dependencies doesn't break our existing projects. Again, you can setup virtual environment however you like but I will keep it simple and will use the following commands:
python3 -m venv env
source env/bin/activate
1.2. Installing packages
Let's install a few packages that we will need. Run the following command to install the required packages. I encourage you to go through their documentation, but I'll try to cover as much as I can in this blog series.
pip install django djangorestframework djangorestframework-simplejwt django-cors-headers psycopg2 python-dotenv dj-database-url google-auth
1.3. Bootstrapping Django project
Let's bootstrap our Django project using the following command:
django-admin startproject <project_name> . # we will call our project confetti
1.4. Spinning up PostgreSQL database
If you wish to use a different database, feel free to do so. I will use PostgreSQL for this project. PostgreSQl is recommended by many in the Django community for various reasons(such as performance, scalability, transaction support, etc.).
This step can change depending on how you want to set up your database. I typically use this command to create a database in my computer:
createdb confetti # confetti is the name of the database/project
If this command doesn't work for you, and you have PostgreSQL installed with pgAdmin, you can use the GUI to create a database. Simply follow these steps:
- Open pgAdmin
- Click on Servers Dropdown -> PostgreSQL
- Right-click on Databases -> Click on Create -> Database
- A modal will appear; add the Database name.
- Click on Save
Once you have your database running, you can make your database url as such:
DATABASE_URL=postgres://<username>:<password>@localhost:<port>/confetti
# replace <username>, <password>, and <port> with your PostgreSQL username, password, and port respectively.
Again, confetti is the name of the database/project.
2. Creating and using virtual environments
2.1. Using environment variables
Create a .env
file in the root of your project and populate the following variables:
SECRET_KEY = "<- secure key ->"
DEBUG = True
DATABASE_URL="postgres://postgres:password@localhost:5432/confetti"
2.2. Update settings.py
Since we are using python-dotenv to manage our environment variables, we need to make some changes to our settings.py
file:
- Import dotenv and load the dotenv. Below is how the code might look:
import os
import dotenv
# Load environment variables from .env file
dotenv_file = os.path.join(BASE_DIR, ".env")
if os.path.isfile(dotenv_file):
dotenv.load_dotenv(dotenv_file)
- Use the environment variables instead of hard-coded values:
SECRET_KEY = os.environ['SECRET_KEY']
DEBUG = os.environ.get('DEBUG', False)
-
Use the PostgreSQL database we created. For that, you will have to do two things:
- Import
dj_database_url
in thesettings.py
file: - Set the default database using the following code
DATABASES = { 'default': dj_database_url.config( default=os.environ.get('DATABASE_URL'), conn_max_age=600, conn_health_checks=True, ) }
- Import
3. Understanding the User Model
Let's go over some member functions and fields to get an understanding of Django's User Model and how to customize it. The primary fields in AbstractUser are:
- username: All usernames must be unique, and this is used by Django for user login.
- first_name & last_name: These fields store the user's first and last names.
- email: Users can have duplicate emails, and it can be left blank.
- is_staff & is_superuser: These fields indicate whether a user is an admin or staff.
- EMAIL_FIELD: This specifies the field used as the email.
- USERNAME_FIELD: This field specifies which field will be used for authentication. By default, it is set to username but you can change it to be email.
- REQUIRED_FIELDS: This field lists the required fields, helpful when creating a superuser with
python3 manage.py createsuperuser
. - Other fields like password, id, etc., are not discussed in detail as they are not as important.
Note: if you want to explore Django's User Model in detail or want to learn more about extending the user model, you can refer to the official documentation here. Alternatively, you can also go through the source code of Django.
If you check the AbstractUser
class, you will see that it inherits from AbstractBaseUser
and PermissionsMixin
. The AbstractBaseUser
class is a minimal class for implementing a user model. It contains logic to hash and check passwords, and have a few more fields that are required for a user model. The PermissionsMixin
class adds the fields and methods necessary to support Django's permission model.
Now, depending on the project requirements, you can do the following:
- Just use the Django default User model.
- Extend
AbstractUser
and add more fields as required or just change theUSERNAME_FIELD
. - Lastly, if you want more control over the user model, you can extend
AbstractBaseUser
andPermissionsMixin
. And define your own fields and methods.
In this blog series, we will go with the third option. We will extend AbstractBaseUser
and PermissionsMixin
to have more control over the user model. Another reason is that it will cover more ground and give us a better understanding of how Django's User model works.
4. Crafting our User Model
Let's create a new app called users
to handle all user-related functionalities. Run the following command to create a new app:
python manage.py startapp users
Now, let's tell Django about our new app. Add the app to the INSTALLED_APPS
list in the settings.py
file:
INSTALLED_APPS = [
...
'users.apps.UsersConfig',
]
Finally, let's create our custom user model. Inside users/models.py, add the following code:
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin,
)
from django.db import models
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
"""Creates and saves a new user"""
if not email:
raise ValueError("Users must have an email address")
user = self.model(email=self.normalize_email(email), **extra_fields)
user.set_password(
password
) # encrypts the password; never do user.password = password because it will be stored as plain text
user.save(using=self._db) # using=self._db is for supporting multiple databases
return user
def create_superuser(self, email, password):
"""Creates and saves a new superuser"""
user = self.create_user(email, password)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
"""Custom user model that supports using email instead of username"""
email = models.EmailField(
max_length=255, unique=True
) # unique=True means that the email must be unique in the database
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
# add more fields & member function if you need for your use-case
objects = UserManager()
USERNAME_FIELD = "email" # this is the field used to log in
def __str__(self):
"""Return string representation of user"""
return f"{self.email}"
Most of the inspiration for above code comes from the Django documentation and by browsing the Django source code.
Before we can run the migrations, we have to update the AUTH_USER_MODEL
in settings.py to point to your custom user model:
AUTH_USER_MODEL = 'users.User'
Finally, run the following commands to apply the changes:
python3 manage.py makemigrations
python3 manage.py migrate
Assuming everything went well, you can create a superuser by running the following command:
python3 manage.py createsuperuser
Before wrapping up this blog, I would like to mention that the code provided is a simplified version, and for a production environment, you should consider additional security and validation measures, as well as any specific requirements for your application.
That's it for the first part of our blog. In the next part, we'll dive into implementing JWT authentication in Django.
Stay tuned for Part 2!