diff --git a/src/Server.php b/src/Server.php index c41b931..bd294d3 100644 --- a/src/Server.php +++ b/src/Server.php @@ -196,6 +196,14 @@ class Server { } // Check that the supplied code_verifier hashes to the stored code_challenge + // TODO: support method = plain as well as S256. + if (!hash_equals($token->getData()['code_challenge'], generatePKCECodeChallenge($bodyParams['code_verifier']))) { + $this->logger->error("The provided code_verifier did not hash to the stored code_challenge"); + return new Response(400, ['content-type' => 'application/json'], json_encode([ + 'error' => 'invalid_grant', + 'error_description' => 'The provided credentials were not valid.' + ])); + } // If everything checked out, return {"me": "https://example.com"} response // (a response containing any additional information must contain a valid scope value, and @@ -444,6 +452,37 @@ class Server { 'error_description' => 'The following required parameters were missing or empty: ' . join(', ', $missingRequiredParameters) ])); } + + // Attempt to internally exchange the provided auth code for an access token. + $token = $this->tokenStorage->exchangeAuthCodeForAccessToken($bodyParams['code']); + + if (is_null($token)) { + $this->logger->error('Attempting to exchange an auth code for a token resulted in null.', $bodyParams); + return new Response(400, ['content-type' => 'application/json'], json_encode([ + 'error' => 'invalid_grant', + 'error_description' => 'The provided credentials were not valid.' + ])); + } + + // Verify that it was issued for the same client_id and redirect_uri + if ($token->getData()['client_id'] !== $bodyParams['client_id'] + || $token->getData()['redirect_uri'] !== $bodyParams['redirect_uri']) { + $this->logger->error("The provided client_id and/or redirect_uri did not match those stored in the token."); + return new Response(400, ['content-type' => 'application/json'], json_encode([ + 'error' => 'invalid_grant', + 'error_description' => 'The provided credentials were not valid.' + ])); + } + + // Check that the supplied code_verifier hashes to the stored code_challenge + // TODO: support method = plain as well as S256. + if (!hash_equals($token->getData()['code_challenge'], generatePKCECodeChallenge($bodyParams['code_verifier']))) { + $this->logger->error("The provided code_verifier did not hash to the stored code_challenge"); + return new Response(400, ['content-type' => 'application/json'], json_encode([ + 'error' => 'invalid_grant', + 'error_description' => 'The provided credentials were not valid.' + ])); + } } return new Response(400, ['content-type' => 'application/json'], json_encode([ diff --git a/src/Storage/FilesystemJsonStorage.php b/src/Storage/FilesystemJsonStorage.php index 064459d..4aa25de 100644 --- a/src/Storage/FilesystemJsonStorage.php +++ b/src/Storage/FilesystemJsonStorage.php @@ -86,7 +86,7 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa if ($data['exchanged_at'] ?? false) { return null; } // Make sure the auth code isn’t expired. - if (($data['valid_until'] ?? 0) < time()) { return null; }echo 'h'; + if (($data['valid_until'] ?? 0) < time()) { return null; } // If the access token is valid, mark it as redeemed and set a new expiry time. $data['exchanged_at'] = time(); diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 80d9b18..3907aba 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -560,7 +560,7 @@ EOT $authEndpointJson = json_decode((string) $authEndpointResponse->getBody(), true); $this->assertEquals('invalid_grant', $authEndpointJson['error']); - $tokenEndpointResponse = $s->handleAuthorizationEndpointRequest($req); + $tokenEndpointResponse = $s->handleTokenEndpointRequest($req); $this->assertEquals(400, $tokenEndpointResponse->getStatusCode()); $tokenEndpointJson = json_decode((string) $tokenEndpointResponse->getBody(), true); $this->assertEquals('invalid_grant', $tokenEndpointJson['error']);