Repository: googleapis/google-auth-library-python

Issue:
Allow multiple audiences for `id_token.verify_token`
 **Is your feature request related to a problem? Please describe.**
When decoding with `google.oauth2.id_token.verify_token` it only excepts one expected audience. This could be an issue when using Cloud IAP, which may require multiple audiences.
 **Describe the solution you'd like**
`verify_token` could allow audience to be a list or a str. If a list verify that the audience is in the list of expected audiences.


Discussion:
arithmetic1728: @derpferd `lint` test failed because the `test_jwt` code has format issue. Could you run `python -m nox -s blacken` to format it (and run `python -m nox -s lint` to check it)? Thanks.

Patch:
diff --git a/google/auth/jwt.py b/google/auth/jwt.py
index 8165dda..892f3a8 100644
--- a/google/auth/jwt.py
+++ b/google/auth/jwt.py
@@ -219,8 +219,9 @@ def decode(token, certs=None, verify=True, audience=None):
             in the token's header.
         verify (bool): Whether to perform signature and claim validation.
             Verification is done by default.
-        audience (str): The audience claim, 'aud', that this JWT should
-            contain. If None then the JWT's 'aud' parameter is not verified.
+        audience (str or list): The audience claim, 'aud', that this JWT should
+            contain. Or a list of audience claims. If None then the JWT's 'aud'
+            parameter is not verified.
 
     Returns:
         Mapping[str, str]: The deserialized JSON payload in the JWT.
@@ -279,9 +280,11 @@ def decode(token, certs=None, verify=True, audience=None):
     # Check audience.
     if audience is not None:
         claim_audience = payload.get("aud")
-        if audience != claim_audience:
+        if isinstance(audience, str):
+            audience = [audience]
+        if claim_audience not in audience:
             raise ValueError(
-                "Token has wrong audience {}, expected {}".format(
+                "Token has wrong audience {}, expected one of {}".format(
                     claim_audience, audience
                 )
             )
diff --git a/google/oauth2/id_token.py b/google/oauth2/id_token.py
index 5e36260..5fbb6a1 100644
--- a/google/oauth2/id_token.py
+++ b/google/oauth2/id_token.py
@@ -112,8 +112,8 @@ def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERT
         id_token (Union[str, bytes]): The encoded token.
         request (google.auth.transport.Request): The object used to make
             HTTP requests.
-        audience (str): The audience that this token is intended for. If None
-            then the audience is not verified.
+        audience (str or list): The audience or audiences that this token is
+            intended for. If None then the audience is not verified.
         certs_url (str): The URL that specifies the certificates to use to
             verify the token. This URL should return JSON in the format of
             ``{'key id': 'x509 certificate'}``.


Test patch:
diff --git a/tests/test_jwt.py b/tests/test_jwt.py
index 7b5ba5c..c5290eb 100644
--- a/tests/test_jwt.py
+++ b/tests/test_jwt.py
@@ -144,6 +144,17 @@ def test_decode_valid_with_audience(token_factory):
     assert payload["metadata"]["meta"] == "data"
 
 
+def test_decode_valid_with_audience_list(token_factory):
+    payload = jwt.decode(
+        token_factory(),
+        certs=PUBLIC_CERT_BYTES,
+        audience=["audience@example.com", "another_audience@example.com"],
+    )
+    assert payload["aud"] == "audience@example.com"
+    assert payload["user"] == "billy bob"
+    assert payload["metadata"]["meta"] == "data"
+
+
 def test_decode_valid_unverified(token_factory):
     payload = jwt.decode(token_factory(), certs=OTHER_CERT_BYTES, verify=False)
     assert payload["aud"] == "audience@example.com"
@@ -211,6 +222,14 @@ def test_decode_bad_token_wrong_audience(token_factory):
     assert excinfo.match(r"Token has wrong audience")
 
 
+def test_decode_bad_token_wrong_audience_list(token_factory):
+    token = token_factory()
+    audience = ["audience2@example.com", "audience3@example.com"]
+    with pytest.raises(ValueError) as excinfo:
+        jwt.decode(token, PUBLIC_CERT_BYTES, audience=audience)
+    assert excinfo.match(r"Token has wrong audience")
+
+
 def test_decode_wrong_cert(token_factory):
     with pytest.raises(ValueError) as excinfo:
         jwt.decode(token_factory(), OTHER_CERT_BYTES)