Authentication with JWT in Django and Djoser: Email Activation, Password Reset, and Best Practices for Building a Production-Ready API

Authentication with JWT in Django and Djoser: Email Activation, Password Reset, and Best Practices for Building a Production-Ready API

As a Software developer working with Django REST Framework and Djoser, I’ve often noticed a common issue among fellow developers: getting email activation links to correctly redirect users to the frontend. By default, Djoser sends an activation link with the backend URL (something like http://localhost:8000/activation/?uid=...&token=...). This is fine if you’re only working with a backend interface, but when there’s a React or Vue frontend involved, this setup falls short.

The problem is simple yet frustrating. A user signs up, receives an activation email, clicks the link — only to be taken to the backend server (which is not where they need to be)! Ideally, that link should lead to http://localhost:3000/activation/... for a React frontend or http://localhost:8080/... for a Vue frontend, where users can complete activation in a polished, user-friendly interface.

I’ve seen countless questions about this in forums, and though there are a few workarounds out there, I haven’t come across a complete guide that walks through how to set up email activation and password reset in a way that integrates smoothly with both Django and a separate frontend. After solving this issue myself, I decided it was time to share a straightforward guide to help others avoid the same headache and get their APIs production-ready with best practices.

In this guide, I’ll walk you through configuring Djoser to send activation links directly to your frontend and handle password resets in a way that ensures a seamless experience for your users. Let’s dive in and make Django +Django REST Framework + Djoser, and JWT a hassle-free setup for your backend project.I will assume you have Python installed on your machine and are familiar with Django, Django REST Framework, and basic Linux commands.

1. Create the Project Directory

Let’s start by creating a project directory called signup_form and navigate into it.


mkdir signup_form
cd signup_form

2. Set Up a Virtual Environment

Create and activate a virtual environment:


python3 -m venv venv
source venv/bin/activate

3. Install Django

Install Django in the virtual environment:


pip install django

4. Create a Django Project

Now, create the Django project within the directory:


django-admin startproject signup_form .

Verify the project runs correctly by starting the development server:

python manage.py runserver

Visit http://localhost:8000 in your browser to confirm the setup. If everything is working, stop the server and proceed.

5. Install Django REST Framework, Djoser, and JWT

Install Django REST Framework, Djoser, and SimpleJWT:


pip install djangorestframework
pip install djoser
pip install djangorestframework_simplejwt

6. Configure Installed Apps and REST Framework Settings

Open your settings.py file, add rest_framework and djoser to the INSTALLED_APPS, and configure the authentication settings as shown below:


INSTALLED_APPS = [
    ...,
    "rest_framework",
    "djoser",
]

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ),
}
DJOSER = {
    "LOGIN_FIELD": "email",
    "SEND_ACTIVATION_EMAIL": True,
    "ACTIVATION_URL": "auth/users/activation/?uid={uid}&token={token}",
    "PASSWORD_RESET_CONFIRM_URL": "auth/users/reset_password_confirm/?uid={uid}&token={token}",
    "SERIALIZERS": {
        "user_create": "core.serializers.CustomUserCreateSerializer",
        "current_user": "core.serializers.CustomUserSerializer",
    },
}
SIMPLE_JWT = {
    "AUTH_HEADER_TYPES": ("JWT",),
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
}
# Set the frontend domain for email links
DOMAIN = 'localhost:3000'

7. Set Up Email Backend

Add email configurations to settings.py (replace placeholder values with your email provider's details):


EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "your_email_host"
EMAIL_PORT = your_email_port
EMAIL_USE_SSL = True
EMAIL_HOST_USER = "your_email"
EMAIL_HOST_PASSWORD = "your_password"
DEFAULT_FROM_EMAIL = "your_default_email"
ADMIN_EMAIL = "your_admin_email"

8. Define URL Patterns

In your urls.py file, include Djoser’s URLs for authentication:


from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("auth/", include("djoser.urls")),
    path("auth/", include("djoser.urls.jwt")),
]

9. Create a Custom User Model

Create a new Django app called core to define a custom user model using email as the unique identifier.


python manage.py startapp core

Add "core.apps.CoreConfig" to INSTALLED_APPS in settings.py.

INSTALLED_APPS = [
    ...,
    "core.apps.CoreConfig",
]

In core/models.py, define the custom user model by extending Django’s AbstractUser:

from django.contrib.auth.models import AbstractUser
from django.db import models
from .managers import CustomUserManager


class CustomUser(AbstractUser):
    email = models.EmailField(unique=True)
    username = models.CharField(max_length=150, blank=True, null=True)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

10. Create a Custom User Manager(managers.py)

In core/managers.py, define the custom user manager:


from django.contrib.auth.models import BaseUserManager

class CustomUserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError("The Email field must be set")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)

        if extra_fields.get("is_staff") is not True:
            raise ValueError("Superuser must have is_staff=True.")
        if extra_fields.get("is_superuser") is not True:
            raise ValueError("Superuser must have is_superuser=True.")

        return self.create_user(email, password, **extra_fields)

11. Create a Custom Serializers.py in core app

In core/serializers.py:


from djoser.serializers import UserCreateSerializer, UserSerializer
from .models import CustomUser


class CustomUserCreateSerializer(UserCreateSerializer):
    class Meta(UserCreateSerializer.Meta):
        model = CustomUser
        fields = ("id", "email", "username", "first_name", "last_name", "password")


class CustomUserSerializer(UserSerializer):
    class Meta(UserSerializer.Meta):
        model = CustomUser
        fields = ("id", "email", "username", "first_name", "last_name")

    def create(self, validated_data):
        user = CustomUser(**validated_data)
        user.is_active = False
        user.save()
        return user

12. Update admin.py of core App

In core/admin.py:


from django.contrib import admin
from .models import CustomUser


class NormalUser(CustomUser):
    class Meta:
        proxy = True


class StaffUser(CustomUser):
    class Meta:
        proxy = True


class NormalUserAdmin(admin.ModelAdmin):
    list_display = ["email", "first_name", "last_name", "is_active"]
    list_filter = ["is_active"]
    search_fields = ["email", "first_name", "last_name"]
    ordering = ["email"]
    readonly_fields = [
        "email",
        "password",
        "first_name",
        "last_name",
        "is_active",
        "last_login",
        "date_joined",
    ]

   
    fieldsets = (
        (
            None,
            {
                "fields": (
                    "email",
                    "password",
                    "first_name",
                    "last_name",
                    "is_active",
                    "date_joined",
                    "last_login",
                )
            },
        ),
        (
            "Staff Status",
            {
                "fields": ("is_staff",),  
            },
        ),
    )

    def get_queryset(self, request):
        return super().get_queryset(request).filter(is_staff=False)

    def make_staff(self, request, queryset):
        """Custom action to make selected users staff."""
        queryset.update(is_staff=True)

    make_staff.short_description = "Make selected users staff"

    actions = [make_staff]


class StaffUserAdmin(admin.ModelAdmin):
    list_display = ["email", "first_name", "last_name", "is_active"]
    list_filter = ["is_active"]
    search_fields = ["email", "first_name", "last_name"]
    ordering = ["email"]
    readonly_fields = [
        "email",
        "password",
        "username",
        "first_name",
        "last_name",
        "is_active",
        "last_login",
        "date_joined",
    ]

    def get_queryset(self, request):
        return super().get_queryset(request).filter(is_staff=True)


admin.site.register(NormalUser, NormalUserAdmin)
admin.site.register(StaffUser, StaffUserAdmin)

13. Update Settings for Custom User Model

In settings.py, specify the custom user model:


AUTH_USER_MODEL = "core.CustomUser"

14. Apply Migrations

Run migrations to apply changes:


python manage.py makemigrations
python manage.py migrate

15. Create a Superuser

Create a superuser to access the admin interface:


python manage.py createsuperuser

16. Start the Server

Run the server to test your setup:


python manage.py runserver

17. Access it here

http://localhost:8000/auth

Congratulations! You should now have a fully functioning API for authentication with email activation, as well as password reset and change capabilities. Make sure your email configuration is set up correctly. iFor more guidance on configuring email, you may find additional tutorials helpful.For those looking to integrate this API with a frontend built using React and TypeScript, check out this GitHub repository for a fully working of backend and frontend example.

If this setup doesn’t work for you, feel free to reach out to me at melakushiferaw05@gmail.com. I’m a software developer and co-founder of Msigana Technologies (msigana.com). For questions, projects, or collaboration opportunities, we’d love to connect. Visit us at msigana.com, or contact me via WhatsApp at +251995030326.

Back to home
Msigana logo

Transforming Ideas into Digital Solutions

Follow us

Contact Us

+2519-9503-0321 info@msigana.com

Airport Rd, Addis Ababa

Important Links

Copyright ©

Msigana.com