Skip to content

Commit

Permalink
Correctly verify PKCE secret in token endpoint
Browse files Browse the repository at this point in the history
Before this change the PKCE secret would be verified only if it was sent
by the client. This defeats the point of PKCE as a malicious actor which
intercepted the code returned from the authorization endpoint would be
able to send a request to the token endpoint without the code_verifier.

This only affects public clients and is subject to the preconditions
described by: https://tools.ietf.org/html/rfc7636#section-1
  • Loading branch information
Zac Pullar-Strecker authored and juanifioren committed Dec 14, 2023
1 parent 935c90d commit 8bfcd47
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 1 deletion.
5 changes: 4 additions & 1 deletion oidc_provider/lib/endpoints/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ def validate_params(self):
raise TokenError('invalid_grant')

# Validate PKCE parameters.
if self.params['code_verifier']:
if self.code.code_challenge:
if self.params['code_verifier'] is None:
raise TokenError('invalid_grant')

if self.code.code_challenge_method == 'S256':
new_code_challenge = urlsafe_b64encode(
hashlib.sha256(self.params['code_verifier'].encode('ascii')).digest()
Expand Down
19 changes: 19 additions & 0 deletions oidc_provider/tests/cases/test_token_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,25 @@ def test_pkce_parameters(self):

json.loads(response.content.decode('utf-8'))

def test_pkce_missing_code_verifier(self):
"""
Test that a request to the token endpoint without the PKCE parameter
fails when PKCE was used during the authorization request.
"""

code = create_code(user=self.user, client=self.client,
scope=['openid', 'email'], nonce=FAKE_NONCE, is_authentication=True,
code_challenge=FAKE_CODE_CHALLENGE, code_challenge_method='S256')
code.save()

post_data = self._auth_code_post_data(code=code.code)

assert 'code_verifier' not in post_data

response = self._post_request(post_data)

assert json.loads(response.content.decode('utf-8')).get('error') == 'invalid_grant'

@override_settings(OIDC_INTROSPECTION_VALIDATE_AUDIENCE_SCOPE=False)
def test_client_credentials_grant_type(self):
fake_scopes_list = ['scopeone', 'scopetwo', INTROSPECTION_SCOPE]
Expand Down

0 comments on commit 8bfcd47

Please sign in to comment.