1313# limitations under the License.
1414
1515import inspect
16+ import json
1617import jwt .utils
1718import secrets
1819import time
@@ -337,16 +338,22 @@ def test_malformed_token_5_iss_liars(self):
337338 under_test .validate_access_token (token = test_jwt ) # No throw
338339
339340 # TC 1
340- # Use the real signing key, but make the iss an invalid type.
341- # You can make the argument this is still valid, because the signature is still
342- # from a trusted issuer. But, we reject it based on bad structure.
343- token_body ["iss" ] = [primary_issuer .token_builder .issuer , untrusted_issuer .token_builder .issuer ]
344- test_jwt = primary_issuer .token_builder .encode (body = token_body , extra_headers = token_header )
341+ # Token with iss set to an invalid type (list instead of string).
342+ # Newer versions of PyJWT prevent encoding non-string iss claims,
343+ # so we use FakeTokenBuilder to craft the malformed token directly.
344+ # The issuer type check happens before signature verification.
345+ fake_jwt = FakeTokenBuilder .fake_token (
346+ body = {
347+ ** token_body ,
348+ "iss" : [primary_issuer .token_builder .issuer , untrusted_issuer .token_builder .issuer ],
349+ },
350+ header = token_header ,
351+ )
345352 with pytest .raises (
346353 InvalidTokenException ,
347354 match = re .escape ("Issuer claim ('iss') must be a of string type. 'list' type was detected." ),
348355 ):
349- under_test .validate_access_token (token = test_jwt )
356+ under_test .validate_access_token (token = fake_jwt )
350357
351358 # TC 2
352359 # Liar token. Untrusted issuer signing key claiming to be valid issuer
@@ -359,13 +366,22 @@ def test_malformed_token_5_iss_liars(self):
359366
360367 # TC 3
361368 # Double-talk liar. Using the untrusted signing key, claiming to be ourselves and the trusted issuer.
362- token_body ["iss" ] = [primary_issuer .token_builder .issuer , untrusted_issuer .token_builder .issuer ]
363- test_jwt = untrusted_issuer .token_builder .encode (body = token_body , extra_headers = token_header )
369+ # We encode a valid token with the untrusted key, then tamper the payload
370+ # post-signing to inject a list iss. This produces a token with a real
371+ # (but untrusted) signature that no longer matches the modified payload —
372+ # a more realistic attack than pure garbage signatures.
373+ legit_jwt = untrusted_issuer .token_builder .encode (body = token_body , extra_headers = token_header )
374+ header_b64 , payload_b64 , signature_b64 = legit_jwt .split ("." )
375+ payload_bytes = jwt .utils .base64url_decode (payload_b64 )
376+ payload = json .loads (payload_bytes )
377+ payload ["iss" ] = [primary_issuer .token_builder .issuer , untrusted_issuer .token_builder .issuer ]
378+ tampered_payload_b64 = jwt .utils .base64url_encode (json .dumps (payload ).encode ("utf-8" )).decode ("utf-8" )
379+ tampered_jwt = f"{ header_b64 } .{ tampered_payload_b64 } .{ signature_b64 } "
364380 with pytest .raises (
365381 InvalidTokenException ,
366382 match = re .escape ("Issuer claim ('iss') must be a of string type. 'list' type was detected." ),
367383 ):
368- under_test .validate_access_token (token = test_jwt )
384+ under_test .validate_access_token (token = tampered_jwt )
369385
370386 def test_missing_signature (self ):
371387 # QE TC11 - JWT without a signature
0 commit comments