Repository: jpadilla/pyjwt

Issue:
{"aud": null } should be ignored
It is a rare case but I found an example that ｛”aud”: null ｝is set.
According to RFC7519(JWT), it is usually a list of strings or a string, but there is no specification about the type of "aud".

## Expected Result

{"aud": null} should be treated the same as unspecified.

## Actual Result

If the ”aud” parameter is not specified, an error will occur.

## Reproduction Steps

See https://dgcg.covidbevis.se/tp/

public key:
https://dgcg.covidbevis.se/tp/cert

JWT:
https://dgcg.covidbevis.se/tp/trust-list


Discussion:

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2c33e32..632d7f9 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/psf/black
-    rev: 21.6b0
+    rev: 21.7b0
     hooks:
       - id: black
         args: ["--target-version=py36"]
@@ -18,7 +18,7 @@ repos:
         language_version: python3.8
 
   - repo: https://github.com/PyCQA/isort
-    rev: 5.8.0
+    rev: 5.9.2
     hooks:
       - id: isort
 
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index b138569..763822a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,6 +13,8 @@ Changed
 Fixed
 ~~~~~
 
+- Fix aud validation to support {'aud': null} case. `#670 <https://github.com/jpadilla/pyjwt/pull/670>`__
+
 Added
 ~~~~~
 
diff --git a/jwt/api_jwt.py b/jwt/api_jwt.py
index 70a5e53..55a8f29 100644
--- a/jwt/api_jwt.py
+++ b/jwt/api_jwt.py
@@ -177,19 +177,18 @@ class PyJWT:
             raise ExpiredSignatureError("Signature has expired")
 
     def _validate_aud(self, payload, audience):
-        if audience is None and "aud" not in payload:
-            return
+        if audience is None:
+            if "aud" not in payload or not payload["aud"]:
+                return
+            # Application did not specify an audience, but
+            # the token has the 'aud' claim
+            raise InvalidAudienceError("Invalid audience")
 
-        if audience is not None and "aud" not in payload:
+        if "aud" not in payload or not payload["aud"]:
             # Application specified an audience, but it could not be
             # verified since the token does not contain a claim.
             raise MissingRequiredClaimError("aud")
 
-        if audience is None and "aud" in payload:
-            # Application did not specify an audience, but
-            # the token has the 'aud' claim
-            raise InvalidAudienceError("Invalid audience")
-
         audience_claims = payload["aud"]
 
         if isinstance(audience_claims, str):


Test patch:
diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py
index 56de90c..1faa05f 100644
--- a/tests/test_api_jwt.py
+++ b/tests/test_api_jwt.py
@@ -202,6 +202,16 @@ class TestJWT:
         with pytest.raises(DecodeError):
             jwt.decode(example_jwt, "secret", algorithms=["HS256"])
 
+    def test_decode_raises_exception_if_aud_is_none(self, jwt):
+        # >>> jwt.encode({'aud': None}, 'secret')
+        example_jwt = (
+            "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9."
+            "eyJhdWQiOm51bGx9."
+            "-Peqc-pTugGvrc5C8Bnl0-X1V_5fv-aVb_7y7nGBVvQ"
+        )
+        decoded = jwt.decode(example_jwt, "secret", algorithms=["HS256"])
+        assert decoded["aud"] is None
+
     def test_encode_datetime(self, jwt):
         secret = "secret"
         current_datetime = datetime.utcnow()
@@ -413,6 +423,15 @@ class TestJWT:
 
         assert exc.value.claim == "aud"
 
+    def test_raise_exception_token_with_aud_none_and_without_audience(self, jwt):
+        payload = {"some": "payload", "aud": None}
+        token = jwt.encode(payload, "secret")
+
+        with pytest.raises(MissingRequiredClaimError) as exc:
+            jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"])
+
+        assert exc.value.claim == "aud"
+
     def test_check_issuer_when_valid(self, jwt):
         issuer = "urn:foo"
         payload = {"some": "payload", "iss": "urn:foo"}