From 36081bf2d402f188854cee914dd4b5083167361a Mon Sep 17 00:00:00 2001 From: Shubham Padkonde Date: Fri, 2 Oct 2026 12:38:04 +0530 Subject: [PATCH] fix: accept string audiences in ID tokens --- intuitlib/utils.py | 6 ++++- tests/test_id_token_audience.py | 47 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/test_id_token_audience.py diff --git a/intuitlib/utils.py b/intuitlib/utils.py index c1fd832..4eb6cb3 100644 --- a/intuitlib/utils.py +++ b/intuitlib/utils.py @@ -153,9 +153,13 @@ def validate_id_token(id_token, client_id, intuit_issuer, jwk_uri): id_token_header = json.loads(b64decode(_correct_padding(id_token_parts[0])).decode('ascii')) id_token_payload = json.loads(b64decode(_correct_padding(id_token_parts[1])).decode('ascii')) + audience = id_token_payload['aud'] + if isinstance(audience, six.string_types): + audience = [audience] + if id_token_payload['iss'] != intuit_issuer: return False - elif id_token_payload['aud'][0] != client_id: + elif audience[0] != client_id: return False current_time = (datetime.utcnow() - datetime(1970, 1, 1)).total_seconds() diff --git a/tests/test_id_token_audience.py b/tests/test_id_token_audience.py new file mode 100644 index 0000000..00e207c --- /dev/null +++ b/tests/test_id_token_audience.py @@ -0,0 +1,47 @@ +"""Exercise audience handling with real RS256 signatures.""" + +import time +from types import SimpleNamespace + +import jwt +import pytest +from cryptography.hazmat.primitives.asymmetric import rsa + +from intuitlib.utils import validate_id_token + + +@pytest.fixture(scope='module') +def signing_key(): + return rsa.generate_private_key(public_exponent=65537, key_size=2048) + + +@pytest.mark.parametrize('audience, expected', [ + ('client-id', True), + (['client-id'], True), + ('other-client', False), + ('client-id-suffix', False), + (['other-client'], False), +]) +def test_id_token_audience(signing_key, monkeypatch, audience, expected): + monkeypatch.setattr('intuitlib.utils.get_jwk', + lambda *_: SimpleNamespace(key=signing_key.public_key())) + token = jwt.encode( + {'iss': 'https://issuer.example', 'aud': audience, + 'exp': int(time.time()) + 3600}, + signing_key, algorithm='RS256', headers={'kid': 'test-key'}, + ) + assert validate_id_token(token, 'client-id', 'https://issuer.example', + 'https://issuer.example/keys') is expected + + +def test_string_audience_still_requires_valid_signature(signing_key, monkeypatch): + other_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + monkeypatch.setattr('intuitlib.utils.get_jwk', + lambda *_: SimpleNamespace(key=other_key.public_key())) + token = jwt.encode( + {'iss': 'https://issuer.example', 'aud': 'client-id', + 'exp': int(time.time()) + 3600}, + signing_key, algorithm='RS256', headers={'kid': 'test-key'}, + ) + assert not validate_id_token(token, 'client-id', 'https://issuer.example', + 'https://issuer.example/keys')