How to blacklist JSON Web Tokens in Django?

On the verge of understanding web application security the first topic that pops up is the authentication. This article will address one of the important topics “token-based authentication” that contributes to web application authentication. The main essence of this article is to understand the issue with the JSON Web Tokens, what is the solution for this issue and how the solution can be implemented in the DjangoREST framework. This article is useful for someone who aims to build or want to increase the security of an existing web application that uses JWT authentication.

These days web applications use token-based authentication to authenticate the user sessions. In brief, in this process, the server generates a token along with some secret and it is stored at the client-side. Every time the client generates a request, this token is sent in the request header. Contemporarily, JSON Web Tokens (JWT) are popularly used for this purpose. The few advantages of using JWT are scalability as the token is stored at the client-side, works with multiple domains as they are included in the header, and so on. Here is the article that precisely explains insights about JWT https://auth0.com/learn/json-web-tokens/

What is the problem with JWT?

The main downside of the JWT is the on-demand revocation before they are expired. In other words, tokens can be used until they are expired. For example, if the user logs out from the application, just by deleting token would not help as the token is still alive and if someone has captured the token, he/she is able to have unauthorized access to the user’s account. According to OWASP, broken authentication is ranked as the second security risk in web application security.

What is the solution?

The solution to this issue is to blacklist is token on demand that is when the user wants to logout, resets password, user deactivates the account, or user is locked out after several unsuccessful logins. In token blacklisting, the valid tokens are stored in the database, when the user wants to logout or he resets his password and so on, the valid token is marked as invalid, even if it is not expired. Hence, if someone has gained the live token when they try to authenticate themselves (after the user has logged out or any above situation occurs), an error will be raised and they won’t be able to gain access to the user account.

How it can be implemented?

We will use the “Simple JWT” library on the DjangoREST framework to implement our solution. This library has all common JWT authentication features including blacklisting.

Note: While selecting the lifetime of tokens, see to it that the lifetime of access or refresh token neither too long nor too short. For example, if the token lifetime is too short then it would result in poor user experience, as the user will be redirected or prompted to re-authenticate itself, this in turn leads to the increase in the credential transmission which makes it more prone to theft.

Installation and setup

Install Simple JWT using the command

pip install djangorestframework-simplejwt

Then add it in authentication classes to inform REST that we will be using this “SimpleJWT” token authentication library

REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
...
}

Note: If you are using any other JWT authentication mechanism you have to remove it from the authentication classes.

Set these parameters as per your requirement in settings.py. There are many parameters, but a few important parameters for our example are:

  • ACCESS_TOKEN_LIFETIME: Specifies the lifetime of the access token. We can specify a lifetime in hours, days, minutes, week, and so on using datetime.timedelta object.
  • REFRESH_TOKEN_LIFETIME: Similar to ACCESS_TOKEN_LIFETIME, here we specify a lifetime for the refresh token. Usually, the lifetime of a refresh token is longer than that of an access token to decrease the credential transmission frequency.
  • ROTATE_REFRESH_TOKENS: This takes values either True or False. When set to True, by sending valid refresh token to the TokenRefreshView you can request a new refresh and access token, and if it is set to False then it returns an only access token.
  • BLACKLIST_AFTER_ROTATION: This takes values either True or False. When it is set to True, then the valid refresh token that is sent to TokenRefreshView is blacklisted, else it’s not blacklisted.
  • AUTH_HEADER_TYPES: Here you can specify the authentication header types like bearer or JWT. For example, Authorization: JWT <token>

For other parameter’s definitions visit here https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html

SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(hours=6),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,'ALGORITHM': 'HS256',
'SIGNING_KEY': settings.SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,'AUTH_HEADER_TYPES': ('Bearer', 'JWT'),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id','AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type','JTI_CLAIM': 'jti','SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=15),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}

In the root url.py include TokenObtainPairView and TokenRefreshView views as shown in the snippet below, as they will form the URL to obtain access and refresh token. In brief operation of these views are

  • TokenObtainPairView: It takes the user credentials — username and password, verifies them, and if valid then access and refresh token is sent, else an error is raised saying “No active account found with the given credentials”.
  • TokenRefreshView: It takes a refresh token and returns a new refresh and access token, only if the sent refresh token is valid. If both the parameters ROTATE_REFRESH_TOKENS and BLACKLIST_AFTER_ROTATION are set to True then the sent valid refresh token is blacklisted and a new refresh and access tokens are returned.

Note: The URL is customizable and can be anything you like.

from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)

urlpatterns = [
...
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
...
]

To obtain tokens you have to send “username” and “password” in the data section as shown

View from the Postman panel
obtaining a token

To get a new pair of tokens by sending a “refresh” token.

Note: This reduces the frequency of sending user credentials or can be used if you have some kind of counter that keeps track of the access token lifetime and instead of redirecting the user to login screen you can request new pair of tokens (using refresh token) allowing the user to still actively use your app.

Image for post
refresh a token

If you want to customize your login, for example, you may be using email instead of a username, you have to inherit the “TokenObtainPairSerializer” and override its method “validate”. This serializer is responsible for validating the user credentials (username and password) and returning tokens on successful validation. Here is a depicting snippet

class MyLogin(TokenObtainPairSerializer):
username_field = 'email'def validate(self, attrs):
"""Overrides validate method in TokenObtainPairSerializer"""

# custom login as per your requirement
# Here, we will check whether the user email is verified, if not return an error
if not email.verified:
return Response("Please verify your email.", status=status.HTTP_400_BAD_REQUEST).data# validate the user credentials returns an error if credentials are not valid
data = super(TokenObtainPairSerializer, self).validate(attrs)return user_dataclass MyTokenObtainView(TokenObtainPairView):
serializer_class = MyLogin

Blacklisting token

To blacklist token include the blacklist app in the installed app list as follows

INSTALLED_APPS = (
...
'rest_framework_simplejwt.token_blacklist',
...
}

After adding the app in the list run “migrate”, to include the related tables.

python manage.py migrate

Here is an example of the usage of blacklist when the user requests the logout:

from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status@api_view(('POST',))
def logout_view(request):
"""Blacklist the refresh token: extract token from the header
during logout request user and refresh token is provided"""Refresh_token = request.data["refresh"]
token = RefreshToken(Rtoken)
token.blacklist()
return Response("Successful Logout", status=status.HTTP_200_OK)

Here the user access token is sent in the Header as “Authorization” parameter and the refresh token is sent in the data section as shown

Image for post
user requesting a logout

Once the user requests a logout, the refresh token is blacklisted, so it can be no longer used to obtain a new pair of tokens. However, note that the access token is still valid, so it is recommended that the lifetime of the access token should be short.

Image for post
failed login because of blacklisted token

GRAD4

At Grad4, all the small details regarding customer data security are taken care of. We ensure that users are actually logged when they request to do so or when they change their credentials, and so on. We ensure that the communication over the platform is safe and at the same time its user friendly. This is accomplished by wrapping the security complexities with smooth and easy navigation on the platform. We also have measures to avoid brute force attacks. This is something really important for every user and could be explained in the next blog article.

References

https://django-rest-framework-simplejwt.readthedocs.io/en/latest/index.html

https://hackernoon.com/all-you-need-to-know-about-user-session-security-ee5245e6bdad

Leave a Reply

Your email address will not be published. Required fields are marked *

Utilisez le formulaire
ci-dessous pour nous écrire

Submit my application

How to blacklist JSON Web Tokens in Django?