How to Add Authentication and Swagger Documentation to Your Django Expense Tracker App | Step-by-Step Guide | Part 3
If you enjoyed this, buy me a coffee! ☕💖
Welcome back to our Expense Tracker series! In this guide, we’ll show you how to add authentication to your Django application, including signup, login, and logout functionalities. Additionally, we’ll integrate Swagger documentation for better API management. Authentication is essential for securing your app and protecting users’ data. We’ll cover creating custom user models, serializers, views, and endpoints.
Step 1: Setup Configuration
Install Required Packages
Ensure you have the following packages installed in your Django project:
pip install djangorestframework
pip install djangorestframework-simplejwt
pip install drf-yasg
pip install django-cors-headers
pip install django-filter
Settings Configuration
Update your settings.py
to include the necessary configurations for installed apps, middleware, and REST framework settings.
from datetime import timedelta
from decouple import config # Ensure you have python-decouple installed for this to work
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['*']
LOG_LEVEL = 'INFO'
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"rest_framework_simplejwt",
"corsheaders",
"django_filters",
"drf_yasg",
"model",
"authentication",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
]
CORS_ALLOW_ALL_ORIGINS = True
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.OrderingFilter',
],
'DEFAULT_PAGINATION_CLASS': 'core.paginators.ExpenseTrackerPaginator',
'PAGE_SIZE': 10,
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': config('SECRET_KEY'),
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ['rest_framework_simplejwt.tokens.AccessToken'],
'TOKEN_TYPE_CLAIM': 'token_type',
}
ROOT_URLCONF = "expense_tracker_project.urls"
AUTH_USER_MODEL = 'model.User'
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / 'templates'],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "expense_tracker_project.wsgi.application"
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'expense_tracker',
'USER': 'db_user',
'PASSWORD': 'db_password',
'HOST': 'localhost',
'PORT': '5432',
}
}
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.mailjet.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
MAILJET_APIKEY_PUBLIC = config('MAILJET_APIKEY_PUBLIC')
MAILJET_APIKEY_PRIVATE = config('MAILJET_APIKEY_PRIVATE')
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
STATIC_URL = "static/"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
Step 2: Define Custom User Model
First, let’s define our user model. Django provides a built-in user model, but for more control, we’ll create a custom user model that uses email instead of a username for authentication.
Create User Model
model/models.py
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils import timezone
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)
return self.create_user(email, password, **extra_fields)
class User(AbstractUser):
email = models.EmailField(unique=True)
username = models.CharField(max_length=30, blank=True, null=True)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
date_joined = models.DateTimeField(default=timezone.now)
is_active = models.BooleanField(default=True)
email_confirmed = models.BooleanField(default=False, verbose_name='Email Confirmed')
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
objects = CustomUserManager()
def __str__(self):
return self.email
Step 3: Create Serializers
We need serializers to handle the data transformation and validation for our sign-up and sign-in views.
Create Serializers
authentication/serializers.py
from django.contrib.auth import get_user_model, authenticate
from rest_framework import serializers
class SignupSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, style={'input_type': 'password'})
class Meta:
model = get_user_model()
fields = ('email', 'first_name', 'last_name', 'password')
def create(self, validated_data):
user = get_user_model().objects.create_user(**validated_data)
user.is_active = False # Make user inactive until email is verified
user.save()
return user
class LoginSerializer(serializers.Serializer):
email = serializers.CharField()
password = serializers.CharField(style={'input_type': 'password'})
def validate(self, data):
email = data.get('email')
password = data.get('password')
if email and password:
user = authenticate(email=email, password=password)
if user:
if user.is_active:
data['user'] = user
else:
raise serializers.ValidationError("User account is disabled.")
else:
raise serializers.ValidationError("Unable to log in with provided credentials.")
else:
raise serializers.ValidationError("Must include 'email' and 'password'.")
return data
Step 4: Create Views
Next, we’ll create views to handle sign-up, sign-in, and logout actions.
Create Views
authentication/views.py
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import logout
from .serializers import SignupSerializer, LoginSerializer
from .models import User
class SignupView(APIView):
def post(self, request, *args, **kwargs):
serializer = SignupSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
# Additional code to send verification email can be added here
return Response({'success': 'User signed up successfully. Verification email sent.'}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class LoginView(APIView):
def post(self, request, *args, **kwargs):
serializer = LoginSerializer(data=request.data)
if serializer.is_valid():
user = serializer.validated_data['user']
refresh = RefreshToken.for_user(user)
access = refresh.access_token
response_data = {
'user_id': user.id,
'email': user.email,
'token': str(access),
'refresh': str(refresh),
}
return Response(response_data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class LogoutView(APIView):
permission_classes = (IsAuthenticated,)
def post(self, request, *args, **kwargs):
logout(request)
return Response({'success': 'User logged out successfully.'}, status=status.HTTP_200_OK)
Step 5: Create URLs
Finally, we’ll set up the URLs for our new views.
Create URLs
authentication/urls.py
from django.urls import path
from .views import SignupView, LoginView, LogoutView
urlpatterns = [
path('signup/', SignupView.as_view(), name='signup'),
path('login/', LoginView.as_view(), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
]
Add these URLs to your project’s main urls.py
file.
expense_tracker_project/urls.py
from django.contrib import admin
from django.urls import path, include
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework.permissions import AllowAny
import authentication.urls as authentication_urls
schema_view = get_schema_view(
openapi.Info(
title="Expense Tracker API",
default_version='v1',
description="Expense Tracker API",
terms_of_service="https://www.example.com/terms/",
contact=openapi.Contact(email="contact@example.com"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=(AllowAny,),
)
urlpatterns = [
path("admin/", admin.site.urls),
path('docs/', include('authentication.swagger')),
path('swagger(?P<format>\.json|\.yaml)',
schema_view.without_ui(cache_timeout=0),
name='schema-json'),
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0),
name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0),
name='schema-redoc'),
]
urlpatterns += core_urls.urlpatterns
urlpatterns += authentication_urls.urlpatterns
Swagger Documentation
Access the Swagger documentation at http://localhost:8000/swagger/
to explore and test your API endpoints.
Conclusion
In this part of our Expense Tracker series, we added essential authentication features to our Django app, including signup, login, and logout functionality. We also integrated Swagger documentation for easier API exploration and testing. These enhancements ensure a more secure and user-friendly application. Stay tuned for the next part, where we’ll cover adding email verification for new users. For more tips and tutorials on Django and REST Framework, follow our series and share your thoughts in the comments!
If you found this article helpful, don’t forget to share it with your friends and colleagues! 📤 Feel free to leave a comment below with your thoughts or questions 💬, and give it a like if you enjoyed it! 👍😊
To get exclusive posts and insights on full-stack projects, become a member on our Patreon page and support our work! 🚀💻
If you enjoyed this, consider buying me a Coffee! ☕💖