Skip to content

Commit a1155ce

Browse files
committed
fix(oauth2): avoid redundant JWKS network fetches
Addresses a performance regression resulting in duplicate fetches of the JWKS endpoint by utilizing the already-fetched certs payload via PyJWKSet.from_dict instead of deferring to PyJWKClient.
1 parent dd30194 commit a1155ce

2 files changed

Lines changed: 20 additions & 10 deletions

File tree

packages/google-auth/google/oauth2/id_token.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def verify_token(
124124
intended for. If None then the audience is not verified.
125125
certs_url (str): The URL that specifies the certificates to use to
126126
verify the token. This URL should return JSON in the format of
127-
``{'key id': 'x509 certificate'}`` or a certificate array according to
127+
``{'key id': 'x509 certificate'}`` or a JWK Set according to
128128
the JWK spec (see https://tools.ietf.org/html/rfc7517).
129129
clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
130130
validation.
@@ -141,8 +141,14 @@ def verify_token(
141141
raise ImportError(
142142
"The pyjwt library is not installed, please install the pyjwt package to use the jwk certs format."
143143
) from caught_exc
144-
jwks_client = jwt_lib.PyJWKClient(certs_url)
145-
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
144+
jwkset = jwt_lib.PyJWKSet.from_dict(certs)
145+
header = jwt_lib.get_unverified_header(id_token)
146+
try:
147+
signing_key = jwkset[header.get("kid")]
148+
except KeyError as caught_exc:
149+
raise ValueError(
150+
"Token has an invalid kid"
151+
) from caught_exc
146152
return jwt_lib.decode(
147153
id_token,
148154
signing_key.key,

packages/google-auth/tests/oauth2/test_id_token.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,24 +86,28 @@ def test_verify_token(_fetch_certs, decode):
8686

8787

8888
@mock.patch("google.oauth2.id_token._fetch_certs", autospec=True)
89-
@mock.patch("jwt.PyJWKClient", autospec=True)
89+
@mock.patch("jwt.PyJWKSet", autospec=True)
90+
@mock.patch("jwt.get_unverified_header", autospec=True)
9091
@mock.patch("jwt.decode", autospec=True)
91-
def test_verify_token_jwk(decode, py_jwk, _fetch_certs):
92+
def test_verify_token_jwk(decode, get_unverified_header, py_jwk_set, _fetch_certs):
9293
certs_url = "abc123"
9394
data = {"keys": [{"alg": "RS256"}]}
9495
_fetch_certs.return_value = data
96+
get_unverified_header.return_value = {"kid": "mock-kid"}
9597
result = id_token.verify_token(
9698
mock.sentinel.token, mock.sentinel.request, certs_url=certs_url
9799
)
98100
assert result == decode.return_value
99-
py_jwk.assert_called_once_with(certs_url)
100-
signing_key = py_jwk.return_value.get_signing_key_from_jwt
101+
py_jwk_set.from_dict.assert_called_once_with(data)
102+
get_unverified_header.assert_called_once_with(mock.sentinel.token)
103+
104+
signing_key = py_jwk_set.from_dict.return_value.__getitem__.return_value
105+
py_jwk_set.from_dict.return_value.__getitem__.assert_called_once_with("mock-kid")
101106
_fetch_certs.assert_called_once_with(mock.sentinel.request, certs_url)
102-
signing_key.assert_called_once_with(mock.sentinel.token)
103107
decode.assert_called_once_with(
104108
mock.sentinel.token,
105-
signing_key.return_value.key,
106-
algorithms=[signing_key.return_value.algorithm_name],
109+
signing_key.key,
110+
algorithms=[signing_key.algorithm_name],
107111
audience=None,
108112
)
109113

0 commit comments

Comments
 (0)