diff --git a/authentik/providers/oauth2/tests/test_token_device.py b/authentik/providers/oauth2/tests/test_token_device.py index 0a02ffdf7e..4a96ab6859 100644 --- a/authentik/providers/oauth2/tests/test_token_device.py +++ b/authentik/providers/oauth2/tests/test_token_device.py @@ -48,6 +48,7 @@ class TestTokenDeviceCode(OAuthTestCase): reverse("authentik_providers_oauth2:token"), data={ "client_id": self.provider.client_id, + "client_secret": self.provider.client_secret, "grant_type": GRANT_TYPE_DEVICE_CODE, }, ) @@ -66,6 +67,7 @@ class TestTokenDeviceCode(OAuthTestCase): reverse("authentik_providers_oauth2:token"), data={ "client_id": self.provider.client_id, + "client_secret": self.provider.client_secret, "grant_type": GRANT_TYPE_DEVICE_CODE, "device_code": device_token.device_code, }, @@ -74,6 +76,26 @@ class TestTokenDeviceCode(OAuthTestCase): body = loads(res.content.decode()) self.assertEqual(body["error"], "authorization_pending") + def test_code_no_auth(self): + """Test code with user""" + device_token = DeviceToken.objects.create( + provider=self.provider, + user_code=generate_code_fixed_length(), + device_code=generate_id(), + user=self.user, + ) + res = self.client.post( + reverse("authentik_providers_oauth2:token"), + data={ + "client_id": self.provider.client_id, + "grant_type": GRANT_TYPE_DEVICE_CODE, + "device_code": device_token.device_code, + }, + ) + self.assertEqual(res.status_code, 400) + body = loads(res.content.decode()) + self.assertEqual(body["error"], "invalid_client") + def test_code(self): """Test code with user""" device_token = DeviceToken.objects.create( @@ -86,6 +108,7 @@ class TestTokenDeviceCode(OAuthTestCase): reverse("authentik_providers_oauth2:token"), data={ "client_id": self.provider.client_id, + "client_secret": self.provider.client_secret, "grant_type": GRANT_TYPE_DEVICE_CODE, "device_code": device_token.device_code, }, @@ -105,6 +128,7 @@ class TestTokenDeviceCode(OAuthTestCase): reverse("authentik_providers_oauth2:token"), data={ "client_id": self.provider.client_id, + "client_secret": self.provider.client_secret, "grant_type": GRANT_TYPE_DEVICE_CODE, "device_code": device_token.device_code, "scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} invalid", diff --git a/authentik/providers/oauth2/views/token.py b/authentik/providers/oauth2/views/token.py index 3e0c6eeacc..17e1e2e5b8 100644 --- a/authentik/providers/oauth2/views/token.py +++ b/authentik/providers/oauth2/views/token.py @@ -165,7 +165,15 @@ class TokenParams: raise TokenError("invalid_grant") def __post_init__(self, raw_code: str, raw_token: str, request: HttpRequest): - if self.grant_type in [GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_REFRESH_TOKEN]: + # Confidential clients MUST authenticate to the token endpoint per + # RFC 6749 §2.3.1. The device code grant (RFC 8628 §3.4) inherits + # that requirement - the device_code alone is not a substitute for + # client credentials. + if self.grant_type in [ + GRANT_TYPE_AUTHORIZATION_CODE, + GRANT_TYPE_REFRESH_TOKEN, + GRANT_TYPE_DEVICE_CODE, + ]: if self.provider.client_type == ClientTypes.CONFIDENTIAL and not compare_digest( self.provider.client_secret, self.client_secret ):