9

Using djangorestframework_simplejwt library, when POST to a custom view

#urls.py
path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain'),

#views.py
class MyTokenObtainPairView(TokenObtainPairView):
    serializer_class = MyTokenObtainPairSerializer

I'm able to get a the following access token

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTkwOTEwNjg0LCJqdGkiOiI3M2MxYmZkOWNmMGY0ZjI3OTY4MGY0ZjhlYjA1NDQ5NyIsInVzZXJfaWQiOjExfQ.5vs0LmNGseU6rtq3vuQyApupxhQM3FBAoKAq8MUukIBOOYfDAV9guuCVEYDoGgK6rdPSIq2mvcSxkILG8OH5LQ

By going to https://jwt.io/ I can see the payload is currently

{
  "token_type": "access",
  "exp": 1590910684,
  "jti": "73c1bfd9cf0f4f279680f4f8eb054497",
  "user_id": 11
}

JWT

So, we can see that the second part of the token is the payload - containing the claims.

I've explored how to add more information to the Response body and now would like to know how to customize the Payload data by adding iat claim, username and today's date.

Community
  • 1
  • 1
Tiago Martins Peres
  • 14,289
  • 18
  • 86
  • 145

3 Answers3

15

As you already created a subclass for the desired view (MyTokenObtainPairView) and a subclass for its corresponding serializer (MyTokenObtainPairSerializer), add the following to the serializer

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):

    ...

    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)

        # Add custom claims
        token['iat'] = datetime.datetime.now()
        token['user'] = user.username
        token['date'] = str(datetime.date.today())

        return token

Then, when you POST to that same location, you'll get an access token like this

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTkwOTE0MTk4LCJqdGkiOiJhZDZmNzZhZjFmOGU0ZWJlOGI2Y2Y5YjQ4MGQzZjY2MiIsInVzZXJfaWQiOjExLCJpYXQiOjE1OTA5MTc0OTgsInVzZXIiOiJ0aWFnbyIsImRhdGUiOiIyMDIwLTA1LTMxIn0.-5U9P-WWmhlOenzCvc6b7_71Tz17LyNxe_DOMwwqH4RqrNsilVukEcZWFRGupLHRZjIvPya2QJGpiju9ujzQuw

Using JWT you can see the Payload changing accordingly

Custom Payload simpleJWT Django

{
  "token_type": "access",
  "exp": 1590914198,
  "jti": "ad6f76af1f8e4ebe8b6cf9b480d3f662",
  "user_id": 11,
  "iat": 1590917498,
  "user": "tiago",
  "date": "2020-05-31"
}
Tiago Martins Peres
  • 14,289
  • 18
  • 86
  • 145
  • 1
    I believe the correct source for the token's issuance timestamp is `token.current_time` instead of `datetime.datetime.now()`. `token.current_time` is calculated when a `Token` instance is created and then used to derive other data such as the expiry. Using `datetime.now()` later on might provide a different timestamp than the one that was originally calculated. – David M. Mar 22 '21 at 13:35
  • 1
    @DavidM. you might be right, yes. The goal here was simply to add new claims, its value was irrelavant. – Tiago Martins Peres Mar 22 '21 at 13:42
  • @Tiago, if I want to send a image along with the refresh and acces token ? Do you maybe have a idea how to do that – Lobbel May 16 '21 at 12:27
  • Is there a way to add IP Address to the payload?? – FreddCha Jun 25 '21 at 15:56
  • @FreddCha yes, you can add pretty much anything you want – Tiago Martins Peres Jun 30 '21 at 11:09
5

In your views.py

from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from decouple import config
from django.contrib.auth import authenticate
import jwt




@api_view(['POST'])
@permission_classes([AllowAny])
def get_tokens_for_user(request):

    username = request.POST.get("username")
    password = request.POST.get("password")

    user = authenticate(username=username, password=password);

    if user is not None:

        refreshToken = RefreshToken.for_user(user)
        accessToken = refreshToken.access_token

        decodeJTW = jwt.decode(str(accessToken), config('SECRET_KEY'), algorithms=["HS256"]);

        # add payload here!!
        decodeJTW['iat'] = '1590917498'
        decodeJTW['user'] = 'tiago'
        decodeJTW['date'] = '2020-05-31'

        #encode
        encoded = jwt.encode(decodeJTW, config('SECRET_KEY'), algorithm="HS256")
    
        return Response({
            'status': True,
            'refresh': str(refreshToken),
            'access': str(encoded),
        })

    else:
        return Response({
            'status': False
        })
        # No backend authenticated the credentials

In your urls.py

from django.urls import path, include
from .views import get_tokens_for_user

urlpatterns = [

        path('login/', get_tokens_for_user, name="login"),
]

In your settings.py

from pathlib import Path
from datetime import timedelta
from decouple import config

...
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY')

# Application definition

INSTALLED_APPS = [
...
    # Rest
    'rest_framework',
...
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]

}

# JWT
# https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'AUTH_HEADER_TYPES': ('Bearer',),
    'SIGNING_KEY': config('SECRET_KEY'),
    'VERIFYING_KEY': config('SECRET_KEY'),
    'ALGORITHM': 'HS256',
}

In your root directory add .env

SECRET_KEY = 'ep@4ojr4m!h73y2j(stackoverflow)kra1*@tq$5el626wf@&p60)7u!6552+-'

Runtime values

 decodeJTW =   {
    'token_type': 'access',
    'exp': 1612651527,
    'jti': '7f415b28610348468ce74ec0f480fad1',
    'user_id': 2,
    'iat': '1590917498',
    'user': 'tiago',
    'date': '2020-05-31'
}

encode = {
   "status":true,
    "refresh":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYxMjczNDU0NywianRpIjoiMDQ0MDI3ZTQzMTc2NDFiNDhhOGI2MjU4MjE4ZGZjNDkiLCJ1c2VyX2lkIjoyfQ.Qf0YfJLAmdYuavDHVng7Bwjmka551G6c1Gi4e-UdRuc",
"access":"b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjEyNjUxNzQ3LCJqdGkiOiI2OWEzNjYwYjYxMTk0MzVjYjljZTA0OGQ3MmE1ODk1YSIsInVzZXJfaWQiOjIsImlhdCI6IjE1OTA5MTc0OTgiLCJ1c2VyIjoidGlhZ28iLCJkYXRlIjoiMjAyMC0wNS0zMSJ9.XUMvhL13zDZdbjYYPkYnwlZoHN6U7Zc3xUzXsKoVj2I'"
    }
BackdoorTech
  • 328
  • 6
  • 7
1

As I came here looking for a solution to add the token expiration to the response while working with dj-rest-auth and djangorestframework_simplejwt I found a solution in the code (apparently undocumented?):

There is a setting called JWT_AUTH_RETURN_EXPIRATION which, if set to True, adds the expiration to the response like so:

{
    "access_token": "ACCESS_TOKEN_STRING",
    "refresh_token": "REFRESH_TOKEN_STRING",
    "user": {
        "pk": PK,
        "email": "USER_EMAIL"
    },
    "access_token_expiration": "2021-02-10T10:40:46.883715Z",
    "refresh_token_expiration": "2021-02-11T10:35:46.883728Z"
}

and same for refresh:

{
    "access": "ACCESS_TOKEN_STRING",
    "access_token_expiration": "2021-02-10T10:47:57.325545Z"
}
MaxMike
  • 137
  • 1
  • 8