I created this custom jwt_payload_handler for jwt so that it can be password aware. If a user changes their password, I want previous tokens to expire. But even though I have a custom handler, for some reason drf jwt ignores the changes and still accepts past tokens after I change the user password or change the password_last_change string value.
Am I missing another configuration?
jwt_payload_handler:
def jwt_payload_handler(user):
username_field = get_username_field()
username = get_username(user)
password_last_change = user.password_last_change # getting json not serializable error. constant string for now
warnings.warn(
'The following fields will be removed in the future: '
'`email` and `user_id`. ',
DeprecationWarning
)
payload = {
'user_id': user.pk,
'username': username,
'password': user.password,
'exp': (datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA) ,
'password_last_change': 'test1',
}
if hasattr(user, 'email'):
payload['email'] = user.email
if isinstance(user.pk, uuid.UUID):
payload['user_id'] = str(user.pk)
payload[username_field] = username
# Include original issued at time for a brand new token,
# to allow token refresh
if api_settings.JWT_ALLOW_REFRESH:
payload['orig_iat'] = timegm(
datetime.utcnow().utctimetuple()
)
if api_settings.JWT_AUDIENCE is not None:
payload['aud'] = api_settings.JWT_AUDIENCE
if api_settings.JWT_ISSUER is not None:
payload['iss'] = api_settings.JWT_ISSUER
return payload
I figured it out. Instead of a custom jwt_payload_handler function we need to set JWT_GET_USER_SECRET_KEY
value in the settings.py file. By default it is None but it accepts a function that takes a user as a parameter and returns a uuid string. If not configured, the conf django secret key is used for all users. But if set, each user will have their own secret key.
On password change, also update the uuid.
in the user model we could set some field to:
class TestUser(AbstractBaseUser):
jwt_secret = models.UUIDField(default=uuid.uuid4)
function example:
def jwt_get_secret_key(user_model):
return user_model.jwt_secret
in settings:
JWT_AUTH = {
...
'JWT_GET_USER_SECRET_KEY': 'path.to.jwt_get_secret_key'
...
}
So in your view or signal, after set_password method is called: you can set the user.secret_jwt to a new value:
user.secret_jwt = uuid.uuid4()
user.save()
Which will make all previous request invalid.