diff --git a/src/Client/Decorator/OAuth2/ClientCredentials.php b/src/Client/Decorator/OAuth2/ClientCredentials.php index fe9684e..9a51066 100644 --- a/src/Client/Decorator/OAuth2/ClientCredentials.php +++ b/src/Client/Decorator/OAuth2/ClientCredentials.php @@ -37,6 +37,16 @@ private function __construct(string $clientID, string $clientSecret, string $sco $this->scope = mb_convert_encoding($scope, 'UTF-8', 'ISO-8859-1'); } + public function getClientId(): string + { + return $this->clientId; + } + + public function getClientSecret(): string + { + return $this->clientSecret; + } + /** * Named constructor to create an instance based on the given credentials * @@ -52,14 +62,17 @@ public static function forHeaderAuthorization(string $clientId, string $clientSe /** * Creates the required key => value pairs for the Access Token Request */ - public function toArray(): array + public function toArray(bool $includeClientCredentials = true): array { $requestData = [ 'grant_type' => 'client_credentials', - 'client_id' => $this->clientId, - 'client_secret' => $this->clientSecret, ]; + if ($includeClientCredentials) { + $requestData['client_id'] = $this->clientId; + $requestData['client_secret'] = $this->clientSecret; + } + if (!empty($this->scope)) { $requestData['scope'] = $this->scope; } diff --git a/src/Client/Decorator/OAuth2/ClientCredentialsDecorator.php b/src/Client/Decorator/OAuth2/ClientCredentialsDecorator.php index 017ed2c..20f23f5 100644 --- a/src/Client/Decorator/OAuth2/ClientCredentialsDecorator.php +++ b/src/Client/Decorator/OAuth2/ClientCredentialsDecorator.php @@ -25,7 +25,9 @@ use Artemeon\HttpClient\Http\Body\Body; use Artemeon\HttpClient\Http\Body\Encoder\FormUrlEncoder; use Artemeon\HttpClient\Http\Header\Fields\Authorization; +use Artemeon\HttpClient\Http\Header\Header; use Artemeon\HttpClient\Http\Header\HeaderField; +use Artemeon\HttpClient\Http\Header\Headers; use Artemeon\HttpClient\Http\MediaType; use Artemeon\HttpClient\Http\Request; use Artemeon\HttpClient\Http\Response; @@ -66,23 +68,34 @@ public function __construct( * @param Uri $uri The Uri object * @param HttpClient $httpClient The http client to decorate * @param AccessTokenCache|null $accessTokenCache AccessTokenCache implementation + * @param bool $strictRfc Strictly follow the OAuth2 spec and send the client credentials only in the Authorization header * * @throws InvalidArgumentException * @throws RuntimeException + * + * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.4.2 */ public static function fromClientCredentials( ClientCredentials $clientCredentials, Uri $uri, HttpClient $httpClient, - AccessTokenCache $accessTokenCache = null + AccessTokenCache $accessTokenCache = null, + bool $strictRfc = false ): self { // Ensure default cache strategy if ($accessTokenCache === null) { $accessTokenCache = new InMemoryAccessTokenCache(); } - $body = Body::fromEncoder(FormUrlEncoder::fromArray($clientCredentials->toArray())); - $accessTokenRequest = Request::forPost($uri, $body); + $headers = Headers::create(); + if ($strictRfc) { + $headers->add(Header::fromField(Authorization::forAuthBasic($clientCredentials->getClientId(), $clientCredentials->getClientSecret()))); + $body = Body::fromEncoder(FormUrlEncoder::fromArray($clientCredentials->toArray(false))); + } else { + $body = Body::fromEncoder(FormUrlEncoder::fromArray($clientCredentials->toArray())); + } + + $accessTokenRequest = Request::forPost($uri, $body, $headers); return new self($httpClient, $accessTokenRequest, $accessTokenCache); }