Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ ignore_missing_imports = True
[mypy-grpc_status]
ignore_missing_imports = True

[mypy-jwt,jwt.*]
ignore_missing_imports = True

[mypy-ibis.*]
ignore_missing_imports = True

Expand Down
23 changes: 19 additions & 4 deletions packages/google-auth/google/oauth2/id_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,25 +124,40 @@ def verify_token(
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'}`` or a certificate array according to
``{'key id': 'x509 certificate'}`` or a JWK Set according to
the JWK spec (see https://tools.ietf.org/html/rfc7517).
clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
validation.

Returns:
Mapping[str, Any]: The decoded token.
"""
if isinstance(id_token, bytes):
id_token = id_token.decode("utf-8")

certs = _fetch_certs(request, certs_url)

if "keys" in certs:
try:
import jwt as jwt_lib # type: ignore
import jwt as jwt_lib
from jwt.api_jwk import PyJWKSet
except ImportError as caught_exc: # pragma: NO COVER
raise ImportError(
"The pyjwt library is not installed, please install the pyjwt package to use the jwk certs format."
) from caught_exc
jwks_client = jwt_lib.PyJWKClient(certs_url)
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
jwkset = PyJWKSet.from_dict(certs)
header = jwt_lib.get_unverified_header(id_token)
kid = header.get("kid")

signing_key = None
for key in jwkset.keys:
if kid and key.key_id == kid and key.public_key_use in ("sig", None):
signing_key = key
break

if signing_key is None:
raise ValueError("Token has an invalid kid")

return jwt_lib.decode(
id_token,
signing_key.key,
Expand Down
23 changes: 16 additions & 7 deletions packages/google-auth/tests/oauth2/test_id_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,33 @@ def test_verify_token(_fetch_certs, decode):


@mock.patch("google.oauth2.id_token._fetch_certs", autospec=True)
@mock.patch("jwt.PyJWKClient", autospec=True)
@mock.patch("jwt.api_jwk.PyJWKSet", autospec=True)
@mock.patch("jwt.get_unverified_header", autospec=True)
@mock.patch("jwt.decode", autospec=True)
def test_verify_token_jwk(decode, py_jwk, _fetch_certs):
def test_verify_token_jwk(decode, get_unverified_header, py_jwk_set, _fetch_certs):
certs_url = "abc123"
data = {"keys": [{"alg": "RS256"}]}
_fetch_certs.return_value = data
get_unverified_header.return_value = {"kid": "mock-kid"}

mock_key = mock.MagicMock()
mock_key.key_id = "mock-kid"
mock_key.public_key_use = "sig"
mock_key.key = mock.sentinel.key
mock_key.algorithm_name = "mock-alg"
py_jwk_set.from_dict.return_value.keys = [mock_key]
result = id_token.verify_token(
mock.sentinel.token, mock.sentinel.request, certs_url=certs_url
)
assert result == decode.return_value
py_jwk.assert_called_once_with(certs_url)
signing_key = py_jwk.return_value.get_signing_key_from_jwt
py_jwk_set.from_dict.assert_called_once_with(data)
get_unverified_header.assert_called_once_with(mock.sentinel.token)

_fetch_certs.assert_called_once_with(mock.sentinel.request, certs_url)
signing_key.assert_called_once_with(mock.sentinel.token)
decode.assert_called_once_with(
mock.sentinel.token,
signing_key.return_value.key,
algorithms=[signing_key.return_value.algorithm_name],
mock.sentinel.key,
algorithms=["mock-alg"],
audience=None,
)
Comment thread
macastelaz marked this conversation as resolved.

Expand Down
Loading