django-rest-framework-jwt icon indicating copy to clipboard operation
django-rest-framework-jwt copied to clipboard

Custom Payload Fields

Open Miserlou opened this issue 9 years ago • 16 comments

It'd be nice to have a setting to set which fields are returned in the token. For instance, I like to use non-sequential user_ids.

Miserlou avatar Aug 13 '15 20:08 Miserlou

Ah, I see you already do this with JWT_PAYLOAD_GET_USER_ID_HANDLER and JWT_RESPONSE_PAYLOAD_HANDLER. Great!

Closing.

Miserlou avatar Aug 13 '15 20:08 Miserlou

The source says JWT_PAYLOAD_GET_USER_ID_HANDLER is going to be deprecated in favor of JWT_PAYLOAD_GET_USERNAME_HANDLER. Is there a timeline for that?

So I imagine the payload will change from: { 'user_id': n, 'token': 'xxx' }

to:

{ 'username': 'jimbo', 'token': 'xxx' }

The problem with this is that I use non-username auth, ex:

{ 'email': '[email protected]', 'token': 'xxx' }

So, we either need to use a custom field and simply call it 'username', or we need a setting the gives us control over the name of the value over the default 'username' field.

Miserlou avatar Aug 13 '15 20:08 Miserlou

This is important for us, I'd love to get an answer on this issue.

Thanks!

Miserlou avatar Oct 13 '15 20:10 Miserlou

I'd also appreciate some options to customize payload. In my case, I need to add more fields to the payload such as full_name, is_owner, language_code. Is this already available for customizing?

JakeMana avatar Feb 06 '16 17:02 JakeMana

@JakubManaPribe Were you able to do it? Second this

sanfilippopablo avatar Jul 07 '16 21:07 sanfilippopablo

@sanfilippopablo sadly not yet but this weekend im going to work more around jwt. Ill let you know later. (Please let me when you will find it out)

JakeMana avatar Jul 19 '16 20:07 JakeMana

Custom payload fields would be really valuable for implementing something a little bit like OAuth scopes, which I would like to do: https://api.slack.com/docs/oauth-scopes

e.g. if the user logs in through a public kiosk (and custom kiosk route), the payload has a 'kiosk' value set, which limits actions to read-only using Django Rest's permissions functions. If they log in using the normal route, the same user has more access to their account.

SamWarmuth avatar Aug 04 '16 14:08 SamWarmuth

I had this problem as well and there is already a way to customise both the token payload and the API Response payload.

Basically, set the following settings in your settings.py

JWT_AUTH = {
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'custom_jwt.jwt_response_payload_handler',
    'JWT_PAYLOAD_HANDLER': 'custom_jwt.jwt_payload_handler',
}

You can define these functions in custom_jwt.py as follows:

from datetime datetime datetime
from calendar import timegm
from rest_framework_jwt.settings import api_settings

def jwt_payload_handler(user):
    """ Custom payload handler
    Token encrypts the dictionary returned by this function, and can be decoded by rest_framework_jwt.utils.jwt_decode_handler
    """
    return {
        'user_id': user.pk,
        'email': user.email,
        'is_superuser': user.is_superuser,
        'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA,
        'orig_iat': timegm(
            datetime.utcnow().utctimetuple()
        )
    }

def jwt_response_payload_handler(token, user=None, request=None):
    """ Custom response payload handler.

    This function controlls the custom payload after login or token refresh. This data is returned through the web API.
    """
    return {
        'token': token,
        'user': {
             'email': user.email,
        }
    }

educalleja avatar Sep 24 '16 06:09 educalleja

@educalleja Thanks for that. I'm having difficulty in decoding and using the additional data. How does one get back the payload from the token? I've added a custom userRole field inside the encoded token which i'd like to decode and check within my custom permissions file

paw1 avatar Mar 16 '17 11:03 paw1

@paw1 I think you can use rest_framework_jwt.utils.jwt_decode_handler to decode the token, and you should obtain the dictionary defined in your custom jwt_payload_handler

educalleja avatar Mar 16 '17 19:03 educalleja

@educalleja is there anyway to set the expiration date of the token based on a condition? Like if the user checks a "remember me" checkbox on the sign-in form.

davidtoluhi avatar May 16 '17 06:05 davidtoluhi

@dakoto747 The jwt library checks the value of the attribute exp to determine whether a token is expired or not.

Have a look at the source code of the function jwt_payload_handler in utils.py. The returned payload contains a key named 'exp' which default value is:

'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA

You can define your custom function jwt_payload_handler as I detailed before and set that value according to a condition.

def jwt_payload_handler(user):
    """ Custom payload handler
    Token encrypts the dictionary returned by this function, and can be decoded by rest_framework_jwt.utils.jwt_decode_handler
    """
    if user.is_remembered:
        expiry_date = datetime.datetime(2018,1,1)   # User's tokens will expiry on new year's day
    else:    # Tokens will expiry after the default expiration time has passed.
        expiry_date = datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA
    return {
        'user_id': user.pk,
        'email': user.email,
        'is_superuser': user.is_superuser,
        'exp': expiry_date,
        'orig_iat': timegm(
            datetime.utcnow().utctimetuple()
        )
    }

I guess you get the idea. Be aware that with this approach a generated token will be valid for a long time and that can be a security threat if a token is stolen.

educalleja avatar May 17 '17 02:05 educalleja

@educalleja Thanks a lot! What I was missing was adding the 'is_remembered' property to my User model(I just wanted to use a post variable from the JS frontend). Also thanks for the security advice! I would be taking that into account.

davidtoluhi avatar May 17 '17 05:05 davidtoluhi

@educalleja I implemented what you had suggested and it worked pretty well for me, I am generating tokens manually as is specified in the docs however I am getting an error when i try to refresh the token or verify it on the endpoint.

 curl -X POST -H "Content-Type: application/json" -d '{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmlnX2lhdCI6MTUwMDI5OTUyNCwidXNlcl9pZCI6IlMxNjQxNzMiLCJleHAiOjE1MDAzODU5MjR9.mTNn44Xm9lBp1v8WkUrkp3kdYSCsOrqHACa7fVq2qi8"}' http://localhost:8000/api-token-verify/
 {"non_field_errors":["Invalid payload."]}%

I did a print of my payload and here is what I get

{'orig_iat': 1500299524, 'user_id': u'S164173', 'exp': 1500385924}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmlnX2lhdCI6MTUwMDI5OTUyNCwidXNlcl9pZCI6IlMxNjQxNzMiLCJleHAiOjE1MDAzODU5MjR9.mTNn44Xm9lBp1v8WkUrkp3kdYSCsOrqHACa7fVq2qi8

I am not using django's default auth mechanism, my auth works like this, i pass in a user id to my api endpoint, it verifies it against an external db, and then sends an otp to the client after the verification of the otp i will return the jwt token to authenticate with other api endpoints.

 payload = jwt_payload_handler(client_id)
 token = jwt_encode_handler(payload)
if (result[0] == otp):
                payload = jwt_payload_handler(client_id)
                access_token = jwt_encode_handler(payload)
                print(payload)
                print(access_token)
                return Response({'message': 'Partner Verified',
                                'access_token': access_token}, 
                                status=status.HTTP_200_OK)

Here is the custom function i implemented based on what you had suggested here.

from datetime import datetime
from calendar import timegm
from rest_framework_jwt.settings import api_settings

def jwt_payload_handler(client_id):
    """ Custom payload handler
    Token encrypts the dictionary returned by this function, and can be decoded by rest_framework_jwt.utils.jwt_decode_handler
    """
    return {
        'user_id': client_id,
        'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA,
        'orig_iat': timegm(
            datetime.utcnow().utctimetuple()
        )
    }

def jwt_response_payload_handler(token, user=None, request=None):
    """ Custom response payload handler.

    This function controlls the custom payload after login or token refresh. This data is returned through the web API.
    """
    return {
        'token': token
    }

Can you or someone please point out what is happening here and what can i do to make it work ?

uber1geek avatar Jul 17 '17 14:07 uber1geek

@uber1geek The only reason I see in the source code why you might receive an Invalid Token error message is when your payload does not contain a username field.

Django-rest-framework-jwt by default checks that a username field is included in the payload. You can see this in the method in class BaseJSONWebTokenAuthentication.authenticate_credentials and the method in class VerificationBaseSerializer._check_user. This is to ensure that the payload contains expected user information and identify a user.

You probably will need to create your own VerifyJSONWebTokenSerializer and RefreshJSONWebTokenSerializer and overwrite the method validate, and use these two serializers on your API to validate the token send in the http request. Because you don't have a username field in your payload, this method raises the ValidationError you can see as "Invalid payload".

educalleja avatar Jul 17 '17 21:07 educalleja

Hey guys, especially @educalleja any comments on https://github.com/GetBlimp/django-rest-framework-jwt/issues/22#issuecomment-470118614 ?

Aameer avatar Mar 06 '19 14:03 Aameer