Skip to content

Commit a2a051d

Browse files
committed
feat(google): Verify id_token signature
1 parent 701bcc6 commit a2a051d

File tree

4 files changed

+36
-7
lines changed

4 files changed

+36
-7
lines changed

Diff for: ChangeLog.rst

+17
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
0.61.1 (unreleased)
2+
*******************
3+
4+
Security notice
5+
---------------
6+
7+
- As part of the Google OAuth handshake, an ID token is obtained by direct
8+
machine to machine communication between the server running django-allauth and
9+
Google. Because of this direct communication, we are allowed to skip checking
10+
the token signature according to the `OpenID Connect Core 1.0 specification
11+
<https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation>`_.
12+
However, as django-allauth is used and built upon by third parties, this is an
13+
implementation detail with security implications that is easily overlooked. To
14+
mitigate potential issues, verifying the signature is now only skipped if it
15+
was django-allauth that actually fetched the access token.
16+
17+
118
0.61.0 (2024-02-07)
219
*******************
320

Diff for: allauth/socialaccount/providers/google/tests.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ def test_extract_data(
307307
(True, True, {"access_token": "123"}, "uid-from-userinfo", "pic-from-userinfo"),
308308
],
309309
)
310+
@pytest.mark.parametrize("did_fetch_access_token", [False, True])
310311
def test_complete_login_variants(
311312
response,
312313
settings_with_google_provider,
@@ -315,6 +316,7 @@ def test_complete_login_variants(
315316
expected_uid,
316317
expected_picture,
317318
id_token_has_picture,
319+
did_fetch_access_token,
318320
):
319321
with patch.object(
320322
GoogleOAuth2Adapter,
@@ -327,16 +329,22 @@ def test_complete_login_variants(
327329
id_token = {"sub": "uid-from-id-token"}
328330
if id_token_has_picture:
329331
id_token["picture"] = "pic-from-id-token"
330-
with patch.object(
331-
GoogleOAuth2Adapter,
332-
"_decode_id_token",
332+
with patch(
333+
"allauth.socialaccount.providers.google.views._verify_and_decode",
333334
return_value=id_token,
334-
):
335+
) as decode_mock:
335336
request = None
336337
app = None
337338
adapter = GoogleOAuth2Adapter(request)
339+
adapter.did_fetch_access_token = did_fetch_access_token
338340
adapter.fetch_userinfo = fetch_userinfo
339341
token = SocialToken()
340342
login = adapter.complete_login(request, app, token, response)
341343
assert login.account.uid == expected_uid
342344
assert login.account.extra_data["picture"] == expected_picture
345+
if not response.get("id_token"):
346+
assert not decode_mock.called
347+
else:
348+
assert decode_mock.call_args.kwargs["verify_signature"] == (
349+
not did_fetch_access_token
350+
)

Diff for: allauth/socialaccount/providers/google/views.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,14 @@ def complete_login(self, request, app, token, response, **kwargs):
9494

9595
def _decode_id_token(self, app, id_token):
9696
"""
97-
Since the token was received by direct communication protected by
97+
If the token was received by direct communication protected by
9898
TLS between this library and Google, we are allowed to skip checking the
9999
token signature according to the OpenID Connect Core 1.0 specification.
100100
101101
https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
102102
"""
103-
return _verify_and_decode(app, id_token, verify_signature=False)
103+
verify_signature = not self.did_fetch_access_token
104+
return _verify_and_decode(app, id_token, verify_signature=verify_signature)
104105

105106
def _fetch_user_info(self, access_token):
106107
resp = (

Diff for: allauth/socialaccount/providers/oauth2/views.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class OAuth2Adapter(object):
3939

4040
def __init__(self, request):
4141
self.request = request
42+
self.did_fetch_access_token = False
4243

4344
def get_provider(self):
4445
return get_adapter(self.request).get_provider(
@@ -67,7 +68,9 @@ def parse_token(self, data):
6768
def get_access_token_data(self, request, app, client):
6869
code = get_request_param(self.request, "code")
6970
pkce_code_verifier = request.session.pop("pkce_code_verifier", None)
70-
return client.get_access_token(code, pkce_code_verifier=pkce_code_verifier)
71+
data = client.get_access_token(code, pkce_code_verifier=pkce_code_verifier)
72+
self.did_fetch_access_token = True
73+
return data
7174

7275

7376
class OAuth2View(object):

0 commit comments

Comments
 (0)