forked from jpadilla/pyjwt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add JWTPayload(dict) for extended verification
The JWTPayload class allows PyJWT.decode() to expose header, signature, signing_input, and compute_hash_digest() (based on header) without changing the pyjwt API in a breaking way. Merely making this info accessible to the client without specifying an additional verification callback scheme is simpler for everyone. Include doc on why JWTPayload is a good idea in a module docstring, since it's a little unusual to subclass `dict`. The intent is to make the JWT payload change as little as possible while still making it easy to add more verification after the fact. Add a simple test for `JWTPayload.compute_hash_digest()` and a test for compute_hash_digest with cryptography (which is compared against a manual hashlib usage). Closes jpadilla#314, jpadilla#295
- Loading branch information
Showing
4 changed files
with
150 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
""" | ||
= JWTPayload | ||
A JWTPayload is the result of PyJWT.decode() | ||
It | ||
- is a dict (namely, the decoded payload) | ||
- has signing_input, header, and signature as attributes | ||
- exposes JWTPayload.compute_hash_digest(<string>) | ||
which selects a hash algo (and implementation) based on the header and uses | ||
it to compute a message digest | ||
== Design Decision: Why JWTPayload? | ||
This implementation path was chosen to handle a desire to support additional | ||
verification of JWTs without changing the API of pyjwt to v2.0 | ||
Because JWTPayload inherits from dict, it behaves the same as the raw dict | ||
objects that PyJWT.decode() used to return (prior to this addition). Unless you | ||
check `type(PyJWT.decode()) is dict`, you likely won't see any change. | ||
It exposes the information previously hidden by PyJWT.decode to allow complex | ||
verification methods to be added to pyjwt client code (rather than baked into | ||
pyjwt itself). | ||
It also allows carefully selected methods (like compute_hash_digest) to be | ||
exposed which are derived from these data. | ||
""" | ||
try: | ||
from cryptography.hazmat.primitives import hashes | ||
from cryptography.hazmat.backends import default_backend | ||
|
||
has_crypto = True | ||
except ImportError: | ||
has_crypto = False | ||
|
||
|
||
class JWTPayload(dict): | ||
""" | ||
A decoded JWT payload. | ||
When treated directly as a dict, represents the JWT Payload (which is | ||
typically what clients want). | ||
:ivar signing_input: The signing input as a bytestring | ||
:ivar header: The JWT header as a **dict** | ||
:ivar signature: The JWT signature as a string | ||
""" | ||
|
||
def __init__( | ||
self, | ||
jwt_api, | ||
payload, | ||
signing_input, | ||
header, | ||
signature, | ||
*args, | ||
**kwargs | ||
): | ||
super(JWTPayload, self).__init__(payload, *args, **kwargs) | ||
self.signing_input = signing_input | ||
self.header = header | ||
self.signature = signature | ||
|
||
self._jwt_api = jwt_api | ||
|
||
def compute_hash_digest(self, bytestr): | ||
""" | ||
Given a bytestring, compute a hash digest of the bytestring and | ||
return it, using the algorithm specified by the JWT header. | ||
When `cryptography` is present, it will be used. | ||
This method is necessary in order to support computation of the OIDC | ||
at_hash claim. | ||
""" | ||
algorithm = self.header.get("alg") | ||
alg_obj = self._jwt_api.get_algo_by_name(algorithm) | ||
hash_alg = alg_obj.hash_alg | ||
|
||
if has_crypto and ( | ||
isinstance(hash_alg, type) | ||
and issubclass(hash_alg, hashes.HashAlgorithm) | ||
): | ||
digest = hashes.Hash(hash_alg(), backend=default_backend()) | ||
digest.update(bytestr) | ||
return digest.finalize() | ||
else: | ||
return hash_alg(bytestr).digest() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters