From f8d71fa30e017b1fcc15ef7dfb609627d7b6ce88 Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Thu, 4 Jun 2026 07:34:12 +0900 Subject: [PATCH] Do not reject present aud claim when audience is None jwt.decode(..., audience=None) is the documented way to skip audience verification, but _validate_aud still ran the final membership check and raised JWTClaimsError("Invalid audience") whenever the token carried an aud claim. RFC 7519 4.1.3 only requires the aud claim to be checked when a principal identifies itself as an intended recipient; a caller that passes no audience is not identifying itself, so a present aud claim must not cause failure. This also matches PyJWT, which skips the check when no audience is supplied. Guard the membership check with audience is not None. A supplied audience still has to match (mismatch raises as before), and verify_aud=False is unaffected. Fixes #389 Signed-off-by: Arpit Jain --- jose/jwt.py | 2 +- tests/test_jwt.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/jose/jwt.py b/jose/jwt.py index f47e4dd..308c564 100644 --- a/jose/jwt.py +++ b/jose/jwt.py @@ -363,7 +363,7 @@ def _validate_aud(claims, audience=None): raise JWTClaimsError("Invalid claim format in token") if any(not isinstance(c, str) for c in audience_claims): raise JWTClaimsError("Invalid claim format in token") - if audience not in audience_claims: + if audience is not None and audience not in audience_claims: raise JWTClaimsError("Invalid audience") diff --git a/tests/test_jwt.py b/tests/test_jwt.py index f9d54cd..6d81f95 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -359,6 +359,38 @@ def test_aud_empty_claim(self, claims, key): token = jwt.encode(claims, key) jwt.decode(token, key, audience=aud) + def test_aud_none_with_aud_claim_present(self, key): + # RFC 7519 4.1.3: a principal only has to identify itself in the + # "aud" claim when it verifies as an intended recipient. A caller + # that passes audience=None is not identifying itself, so a present + # "aud" claim must not cause verification to fail. See issue #389. + claims = {"aud": "audience"} + + token = jwt.encode(claims, key) + jwt.decode(token, key, audience=None) + + def test_aud_none_with_aud_list_claim_present(self, key): + claims = {"aud": ["audience", "another"]} + + token = jwt.encode(claims, key) + jwt.decode(token, key, audience=None) + + def test_aud_none_default_argument(self, key): + # audience defaults to None, so omitting it entirely must also pass + # when the token carries an "aud" claim. + claims = {"aud": "audience"} + + token = jwt.encode(claims, key) + jwt.decode(token, key) + + def test_aud_verify_disabled_with_aud_claim(self, key): + # verify_aud=False must still skip the check even when an audience + # is supplied and would otherwise mismatch. + claims = {"aud": "audience"} + + token = jwt.encode(claims, key) + jwt.decode(token, key, audience="other", options={"verify_aud": False}) + def test_aud_not_string_or_list(self, key): aud = 1