By using our website, you agree to our privacy policy

Ruddra.com

Create an Application with JWT based Authentication in 10 Minutes using Django

Create an Application with JWT based Authentication in 10 Minutes using Django
Photo by Mae Mu on Unsplash

Let us say, you need an authentication service in a rush, which needs to be cutting edge and uses JWT based authentication. Or you need a stand alone authentication service to be plugged in with other microservices. Do not worry, you can create one in 10 minutes. All you need to have is Python and Docker installed in your machine. 10 minutes may sound a bit exaggerating but trust me, it should not take longer than that 😊. In this article, I am going to share how you can do this in a few simple steps.

FYI, for this project you do not need prior django knowledge. Just having a coding background should be good enough or some Python syntax knowledge would be nice. Without further ado, let us start our stopwatch and begin:

Install CookieCutter

First, we need to install the cookiecutter. It allows us to create projects from templates. To install it, we need to run:

python3 -m pip install cookiecutter --user

Create Django project

Now we are going to create the django project using cookiecutter. We are going to use cookiecutter-django as template project:

cookiecutter https://github.com/pydanny/cookiecutter-django

It will prompt for many options. Here is my setup:

Config

We have prompted yes for Django Rest Framework and Docker. Rest of the setup can be based on your preference. Cool, now we have our boilerplate project.

Setup virtual environment(optional)

This is an optional step, only required if you want to run this project outside of docker.

Let us set up a virtual environment using Python’s build in venv and activate it:

python3 -m venv venv
source venv/bin/activate

Now we can install dependencies inside the project:

pip install -r requirements/local.txt

FYI, some dependency installation might throw errors. Use google to resolve them 😏.

Setup RESTful API

Now we have our django application, let us integrate API. We already have Django Rest Framework pre-installed with cookiecutter, so we are going to use a library which utilizes that and provide authentication. My preference is djoser.

According to their documentation, let us add the app in config/settings/base.py, like this:

THIRD_PARTY_APPS = [
    "crispy_forms",
    "allauth",
    "allauth.account",
    "allauth.socialaccount",
    "rest_framework",
    "djoser"
    # "rest_framework.authtoken", #<---
]

We are commenting out rest_framework.authtoken(marked with #<---) from THIRD_PARTY_APPS, as we plan to use JWT based authentication.

Let us change in config/urls.py as well:

urlpatterns += [
    # API base url
    path("api/", include("config.api_router")),
    # DRF auth token
    # path("auth-token/", obtain_auth_token),  #<--

    # djoser auth
    path("auth/", include('djoser.urls')),
]

Keep in mind that cookiecutter-django provides a token based authentication. As our plan is to use JWT based authentication, so we will be removing token based authentication related codes from the project(marking by #<-- at the end).

Setup JWT authentication

Our project is ready for integration with JWT based authentication. For that, we are going to use django-rest-framework-jwt:

Let us add JWTAuthentication authentication class to the DEFAULT_AUTHENTICATION_CLASSES in config/settings/base.py:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ),
    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
}

We need to add token generation url paths to config/urls.py:

urlpatterns += [
    path('auth/', include('djoser.urls')),
    path('auth/', include('djoser.urls.jwt')),
]

Now, let us add the new dependencies in requirements/base.txt file:

djangorestframework-simplejwt==4.4.0
djoser==2.0.3

(Optional) You can install it in virtual environment by:

pip install djoser
pip install djangorestframework_simplejwt

Please check the documentation regarding API endpoints.

Spin up the docker

Let us start our docker containers using docker-compose. If you don’t have it, then install it from docker-compose installation guide. My suggestion is to use pip:

python -m pip install docker-compose --user

Now, let us start the development server by(from root of the project):

docker-compose -f local.yml up

Awesome, we have our project running. For production server use:

docker-compose -f production.yml up

But, you need to keep in mind, if you want mail sending functionality, make sure to add them either in .envs/.product/.django file or set as an environment variable in *.yml file.

Run outside of docker(optional)

This is an optional step because we are assuming you will be using docker.

If you have added environment variables properly(in .envs folder or in environment variable), then run:

python manage.py migrate

To populate tables inside the database. To run the project, use:

python manage.py runserver

FYI, it is a development server. For production grade servers, use gunicorn or uwsgi.

Miscellaneous

Okay, now our project is running. So let us clean it up a little. Also, some of the setup might be a bit complex and may require some django knowledge.

Rebuild docker images

If you have changed something inside code, better rebuild the docker images. You can do that by:

docker-compose -f local.yml build

Remove unnecessary dependencies

This step is useful if you want to make this application only as a REST API based backend.

We are going to clean up codes regarding django allauth and crispy form. Just remove them from THIRD_PARTY_APPS inside config/settings/base.py .

THIRD_PARTY_APPS = [
    # "crispy_forms",  #<---
    # "allauth",  #<---
    # "allauth.account",  #<---
    # "allauth.socialaccount",  #<---
    "rest_framework",
    "rest_framework.authtoken",
]

Also, we need to remove some unnecessary codes from template passport/templates/base.html. Otherwise it will cause errors because of missing libraries. Let us replace it with something simple:

<html>
  <body>
    <h1>Hello from base</h1>
  </body>
</html>

We also need to clean up allauth related codes from config/urls.py:

urlpatterns = [
    path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
    path(
        "about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
    ),
    # Django Admin, use {% url 'admin:index' %}
    path(settings.ADMIN_URL, admin.site.urls),
    # User management
    path("users/", include("passport.users.urls", namespace="users")),
    # path("accounts/", include("allauth.urls")),  #<--
    # Your stuff: custom urls includes go here
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Commented out part is marked by #<--.

Remove unnecessary codes from urls.py

The config/urls.py is in untidy condition. Let us replace it with following:

from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
from django.views import defaults as default_views
from django.views.generic import TemplateView

urlpatterns = [
    path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
    # Django Admin, use {% url 'admin:index' %}
    path(settings.ADMIN_URL, admin.site.urls),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

# API URLS
urlpatterns += [
    # DRF auth token
    path("auth-token/", obtain_auth_token),
]

if settings.DEBUG:
    # This allows the error pages to be debugged during development, just visit
    # these url in browser to see how these error pages look like.
    urlpatterns += [
        path(
            "400/",
            default_views.bad_request,
            kwargs={"exception": Exception("Bad Request!")},
        ),
        path(
            "403/",
            default_views.permission_denied,
            kwargs={"exception": Exception("Permission Denied")},
        ),
        path(
            "404/",
            default_views.page_not_found,
            kwargs={"exception": Exception("Page not Found")},
        ),
        path("500/", default_views.server_error),
    ]
    if "debug_toolbar" in settings.INSTALLED_APPS:
        import debug_toolbar
        urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns

Add social authentication

As per documentation of djoser, you can add social authentication using social-auth-app-django. You can add it to requirements to requirements/base.txt by:

social-auth-app-django==3.1.0

(Optional) Install it in virtual environment by:

pip install social-auth-app-django==3.1.0

You need to configure the social backends to accommodate which oauth services you will be using. The api will be accessible from http://localhost:800/auth/o/{{provider}}. Check the social endpoints provided by djoser documentation.

Add API documentation

We can easily add some API documentation to our project. My choice is to use a solution based on reDoc, for example: drf-yasg. We can simply add the dependency in requirements/local.txt:

drf-yasg==1.17.1

(Optional) Install it in virtual environment by:

pip install drf-yasg==1.17.1

And add the following codes at the bottom of config/urls.py:

if "drf_yasg" in settings.INSTALLED_APPS:
    from drf_yasg.views import get_schema_view
    from drf_yasg import openapi
    from rest_framework import permissions
    schema_view = get_schema_view(
        openapi.Info(
            title="Snippets API",
            default_version='v1',
            description="Test description",
            terms_of_service="https://www.google.com/policies/terms/",
            contact=openapi.Contact(email="[email protected]"),
            license=openapi.License(name="BSD License"),
        ),
        public=True,
        permission_classes=(permissions.AllowAny,),
    )

    urlpatterns = [
        path('redoc/', schema_view.with_ui('redoc', cache_timeout=0),
                name='schema-redoc'),
    ] + urlpatterns

Change header

By default, HEADER will look be like this JWT <XXXXXXXX>, which is not Bearer schema. You can change it to Bearer <XXXXXXX> by adding the following lines in config/settings/base.py:

SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('Bearer',),
}

Tweak authentication service

You can change authentication service settings, like if it will send email or not. From djoser documentation, here is an example settings(in config/settings/base.py):

DJOSER = {
    'PASSWORD_RESET_CONFIRM_URL': '#/password/reset/confirm/{uid}/{token}',
    'USERNAME_RESET_CONFIRM_URL': '#/username/reset/confirm/{uid}/{token}',
}

Why wait 10 minutes when you can clone it

Assuming you do not want to take the trouble of creating the project from scratch, then simply clone my project from github and spin up the docker.

In conclusion

Steps till miscellaneous took me around 5-10 minutes. But it may take longer depending on various conditions like on internet speed, machine configuration and prior python/django knowledge. But, this solution is top notch and you can easily deploy it with docker anywhere. You can also plug it in as a microservice and handle authentication smoothly.

Thank you for reading. Let me find your experience in the comment section below.

Last updated: May 27, 2020

  • x15

x15

Share Your Thoughts
M ↓   Markdown