jpadilla / pyjwt

JSON Web Token implementation in Python
https://pyjwt.readthedocs.io
MIT License
5.05k stars 676 forks source link

JWT encode doesn't work when key 'expires' exists in payload #853

Closed cesarqdt closed 1 year ago

cesarqdt commented 1 year ago

Summary. There is a bug in the json.dumps part of the code when trying to encode a payload if the payload contains the key expires and raises a TypeError TypeError: Object of type datetime is not JSON serializable. The payload gets encoded correctly if the key expires is changed to exp only.

Expected Result

expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours = 12)
user_id = 1
payload = {'user_id': user_id,
            'expires': expires,
            'allowed_resources': ['__all__']}
payload = jwt.encode(payload, 'secret-key', algorithm = 'HS256')
print(payload)
>>> eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoxLCJleHAiOjE2NzQyNzM0ODksImFsbG93ZWRfcmVzb3VyY2VzIjpbIl9fYWxsX18iXX0.Fmh7mTQHV-CVg3RsxOzbOVkElAKvjhR65QtqUbIkpsg

Actual Result

expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours = 12)
user_id = 1
payload = {'user_id': user_id,
            'expires': expires,
            'allowed_resources': ['__all__']}
payload = jwt.encode(payload, 'secret-key', algorithm = 'HS256')
>>> 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~/auth.py in <module>
      4             'expires': expires,
      5             'allowed_resources': ['__all__']}
----> 6 payload = jwt.encode(payload, 'secret-key', algorithm = 'HS256')

.venv/lib/python3.9/site-packages/jwt/api_jwt.py in encode(self, payload, key, algorithm, headers, json_encoder)
     58                 payload[time_claim] = timegm(payload[time_claim].utctimetuple())
     59 
---> 60         json_payload = json.dumps(
     61             payload, separators=(",", ":"), cls=json_encoder
     62         ).encode("utf-8")

~/.pyenv/versions/3.9.15/lib/python3.9/json/__init__.py in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
    232     if cls is None:
    233         cls = JSONEncoder
--> 234     return cls(
    235         skipkeys=skipkeys, ensure_ascii=ensure_ascii,
    236         check_circular=check_circular, allow_nan=allow_nan, indent=indent,

~/.pyenv/versions/3.9.15/lib/python3.9/json/encoder.py in encode(self, o)
    197         # exceptions aren't as detailed.  The list call should be roughly
    198         # equivalent to the PySequence_Fast that ''.join() would do.
--> 199         chunks = self.iterencode(o, _one_shot=True)
    200         if not isinstance(chunks, (list, tuple)):
    201             chunks = list(chunks)

~/.pyenv/versions/3.9.15/lib/python3.9/json/encoder.py in iterencode(self, o, _one_shot)
    255                 self.key_separator, self.item_separator, self.sort_keys,
    256                 self.skipkeys, _one_shot)
--> 257         return _iterencode(o, 0)
    258 
    259 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,

~/.pyenv/versions/3.9.15/lib/python3.9/json/encoder.py in default(self, o)
    177 
    178         """
--> 179         raise TypeError(f'Object of type {o.__class__.__name__} '
    180                         f'is not JSON serializable')
    181 

TypeError: Object of type datetime is not JSON serializable

If I change the key expires for exp in the payload, the code works as expected.

expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours = 12)
user_id = 1
payload = {'user_id': user_id,
            'exp': expires,
            'allowed_resources': ['__all__']}
payload = jwt.encode(payload, 'secret-key', algorithm = 'HS256')
print(payload)
>>> eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoxLCJleHAiOjE2NzQyNzM0ODksImFsbG93ZWRfcmVzb3VyY2VzIjpbIl9fYWxsX18iXX0.Fmh7mTQHV-CVg3RsxOzbOVkElAKvjhR65QtqUbIkpsg
haquem1 commented 1 year ago

@cesarqdt See PyJWT docs

exp is a registered claim name. datetime actually isn't JSON serializable. Scrolling down to Expiration Time Claim (exp), you will see that the library actually converts datetime objects into a UNIX timestamp.

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days