How to create blacklist for JWT tokens in Django

header = { "alg": "HS256", "typ": "JWT"}
  • iss: a string with the unique identifier of the party generating the token.
  • sub: a string that is the unique identifier of the party about which information is contained in this token (subject).
  • aud: an array of case-sensitive strings or a URI that is a list of the recipients of this token.
  • exp: A time in Unix Time format that determines when the token will become invalid (expiration).
  • nbf: opposite to the exp key, this is a Unix Time that determines when the token will become valid (not before).
  • jti: a string that specifies the unique identifier for this token (JWT ID).
  • iat: a time in Unix Time format that specifies when the token was created.
INSTALLED_APPS = [

'users.apps.UsersConfig',
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=2),
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True
}
INSTALLED_APPS = [

'users.apps.UsersConfig',
'rest_framework',
'rest_framework_simplejwt',
'rest_framework_simplejwt.token_blacklist',
]
from django.contrib import admin
from django.urls.conf import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/users/', include('users.urls'))
]
from typing import Any, Type, Union
from django.contrib.auth.base_user import BaseUserManager
class UserManager(BaseUserManager):"""
Manager for overriding users model
"""
use_in_migrations = Truedef create_user(self, email: str, password: str, **kwargs: Union[str, Any]) -> Type[BaseUserManager]:"""
Managers method for default user creation
"""
if not email:
raise ValueError("Please, input email address")
email = self.normalize_email(email)
user = self.model(email=email, **kwargs)
user.set_password(password)
user.save(using=self._db)
return userdef create_superuser(self, email: str, password: str, **params: Union[str, Any]) -> Type[BaseUserManager]:"""
Managers method for superuser creation
"""
params.setdefault("is_staff", True)
params.setdefault("is_superuser", True)
params.setdefault("is_active", True)
if params.get("is_staff") is not True:
raise ValueError("superuser must have a is_staff=True")
if params.get("is_superuser") is not True:
raise ValueError("superuser must have a is_superuser=True")
return self.create_user(email, password, **params)
from typing import List
from django.db import models
from django.contrib.auth.models import AbstractUser
from users.managers import UserManager
from rest_framework_simplejwt.tokens import RefreshToken
class User(AbstractUser):
"""
[User]
Overridden user class with custom manager.
"""
username = None
# Email field will be used to identify user in the system
email = models.EmailField(unique=True)
# Specifies which field is used to login
USERNAME_FIELD = "email"
REQUIRED_FIELDS: List = []
# Specifies which manager to use for this model
objects = UserManager()
class Meta:
verbose_name = "Пользователь"
verbose_name_plural = "Пользователи"
app_label = 'users'
@property
def access_token(self) -> str:
"""
Allows you to get an access token from an instance of the User model
:return: str
"""
return str(RefreshToken.for_user(self).access_token)
@property
def refresh_token(self) -> str:
"""
Allows you to get a refere token from an instance of the User model.
:return: str
"""
return str(RefreshToken.for_user(self))
def __str__(self) -> str:
"""
:returns:
[str]: Responsible for the correct display of the object.
"""
return self.email
AUTH_USER_MODEL = "users.User"

Receiving tokens, user registration, user information

from typing import Dict
from rest_framework import serializers
from users.models import User
class RegistrationSerializer(serializers.ModelSerializer):
"""
Serializer for new user registration
"""
password = serializers.CharField(
max_length=128,
min_length=8,
write_only=True
)
access_token = serializers.CharField(max_length=255, read_only=True)
refresh_token = serializers.CharField(max_length=255, read_only=True)
class Meta:
model = User
fields = ['email', 'first_name', 'last_name', 'password', 'access_token', 'refresh_token']
def create(self, validated_data: Dict) -> User:
# The method from the custom manager is used
return User.objects.create_user(**validated_data)
class UserInfoSerializer(serializers.ModelSerializer):
"""
Serializer to get basic information about the user
"""
class Meta:
model = User
fields = ['email', 'first_name', 'last_name']
from rest_framework import status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from users.serializers import RegistrationSerializer, UserInfoSerializer
class RegistrationAPIView(APIView):# All users must have access to registration
permission_classes = [AllowAny]
serializer_class = RegistrationSerializer
def post(self, request: Request) -> Response:serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)class UserInfoAPIView(APIView):serializer_class = UserInfoSerializer
permission_classes = [IsAuthenticated]
def get(self, request: Request) -> Response:return Response(self.serializer_class(request.user).data, status=status.HTTP_200_OK)
from django.urls.conf import path
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)

from users.views import RegistrationAPIView, UserInfoAPIView, ResetTokenAPIView

urlpatterns = [
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('registartion/', RegistrationAPIView.as_view(), name='registartion'),
path('info/', UserInfoAPIView.as_view(), name='info'),
]

Adding existing tokens to the “Black List”

from rest_framework_simplejwt.token_blacklist.models import OutstandingToken, BlacklistedToken
class ResetTokenAPIView(APIView):
"""
Adding all refresh tokens in black list
"""
def post(self, request: Request) -> Response:
tokens = OutstandingToken.objects.filter(user_id=request.user.id)
for token in tokens:
t, _ = BlacklistedToken.objects.get_or_create(token=token)
return Response(status=status.HTTP_205_RESET_CONTENT)
from users.views import RegistrationAPIView, UserInfoAPIView, ResetTokenAPIViewurlpatterns = [
...
path('reset-all-token/', ResetTokenAPIView.as_view(), name='reset-all-token')
]

Testing the resulting API with Postman

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store