Provide your own Token Manager System with Django Rest Framework (like Telegram)

Reza Torkaman Ahmadi
4 min readAug 27, 2020

--

If you have a good experience with django rest framework & you used django-restframework-jwt for JWT Authorization of your Rest APIs before; You may faced this concern how we can delete a token if needed. For example build a session management system like telegram which you can delete any session any time you want on demand. If you use pure jwt, you can’t use it. because even you delete the token, it is still valid until it’s expiration date arrive.

In this article I will describe on my packages which is A Django Rest Token Authentication system like telegram which will be using JWT as core with extended features.

You can access django-token-manager from my github repo:

Features

  • Validation of tokens first with jwt algorithms to filter not valid token formats, before hitting database.
  • API to see list of active tokens of each user_id
  • API to delete each token if needed
  • API to remove all other tokens of each users and keep just existing one
  • Fetch useful info for each token request like os, ip and …

Why django-token-manager

Reason to use this module is that by default if you are using jwt system for token authorization of client, you don’t have control on existing tokens. Of course you can set a expire date for each token. But if the expiration date isn’t arrived yet, you can’t delete this token. What happens if you want to delete all sessions of a user. With jwt you don’t have control on it, and you should wait for expiration of token to be arrived.

The purpose of this package, is to give more control on jwt tokens. For this there will be a lookup_id in payload of each jwt token. First token with be validated with jwt algorithms. Then payload lookup_id will be checked on database and if available will give access. And with this solution no need to query on a big string (session string) on database, if the jwt token is valid, will just query on a db_index ed field lookup_id.

How it works!

The logic is simple as follow:

  • We will create token with same logic as djangorestframework-jwt package. But we always will inject a lookup_id in payload of jwt. By this whenever user request with the provided token, we will first validate it see if jwt token is in valid format or not.
  • Next we will check lookup_id by hitting database. If the relation between user and lookup_id does exist, means that the token is already created for user, but is not removed yet. So will authorize user.
  • And whenever we want to delete a token for specific user, we can simply delete this relation from database. So even jwt token is valid, but it’s not authorized with database result.

By using this logic we can create a complete authentication system with full crud on tokens and on the other side instead of query on a long session value, we just need to lookup on a simple integer which is lookup_id.

Database model

A base model called TokenLookupID is used to store each token created for each user. id is auto incremented whenever a new token is created. each id is mapped to a user.

class TokenLookUpID(models.Model):
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
ip = models.CharField(max_length=20, null=True, blank=True)
os = models.CharField(max_length=300, null=True, blank=True)
r_type = models.CharField(max_length=20, null=True, blank=True)
device = models.CharField(max_length=20, null=True, blank=True)
browser = models.CharField(max_length=30, null=True, blank=True)

whenever user authenticates & we want to create a token for him, a relation for user will be created in TokenLookUpID model.

Logic in views.py

When user try to authenticate for example with username and password, JWT Token is first validated to check if it is correct. The code for validation and checking lookup_id is as follow:

class VerifyJSONWebTokenSerializer(VerificationBaseSerializer):
"""
Check the veracity of an access token.
"""

def post_task_after_token_valid(self, token_obj):
# set new ip address
try:
new_ip = get_client_ip(self.context.get('request'))
token_obj.ip = new_ip
except:
pass

token_obj.save()

def _check_payload(self, token):
# Check payload valid (based off of JSONWebTokenAuthentication,
# may want to refactor)
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise serializers.ValidationError(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise serializers.ValidationError(msg)

return payload

def _check_user(self, payload):
username = jwt_get_username_from_payload(payload)

if not username:
msg = _('Invalid payload.')
raise serializers.ValidationError(msg)

# Make sure user exists
try:
user = User.objects.get_by_natural_key(username)
except User.DoesNotExist:
msg = _("User doesn't exist.")
raise serializers.ValidationError(msg)

if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)

return user

def validate(self, attrs):
token = attrs['token']

payload = self._check_payload(token=token)
user = self._check_user(payload=payload)

lookup_id = payload.get('lookup_id')
user_id = user.id
token_obj = TokenLookUpID.objects.filter(user__id=user_id, id=lookup_id).first()
if token_obj:
self.post_task_after_token_valid(token_obj)
return {
'token': token,
'user': user
}
else:
raise ValidationError('Token is not valid!)

If the token is valid, will authenticate user with provided authentication class. Then will generate token and return to user.

Now there a token registered in database and we can control from admin panel. For example below shows that admin is authenticated with our token system. which is using Linux and Firefox .

admin panel view for generated token

Just by deleting this token from admin panel, token will not be available anymore and will not work.

Hope this article was useful. peace :)

--

--

Reza Torkaman Ahmadi
Reza Torkaman Ahmadi

Written by Reza Torkaman Ahmadi

CTO & Co-Founder @ CPOL; A CTF enthusiast & believer in rough consensus and running codes. A person who loves to learn about whatever that makes him excited;)

No responses yet