Skip to content

Commit

Permalink
Merge pull request #422 from patrickcarlohickman/add-client-credentia…
Browse files Browse the repository at this point in the history
…ls-with-basic-auth

Add client credentials grant with basic auth
  • Loading branch information
Sammyjo20 authored Aug 3, 2024
2 parents 9d2b1c6 + 3019489 commit 74b7f67
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 0 deletions.
67 changes: 67 additions & 0 deletions src/Http/OAuth2/GetClientCredentialsTokenBasicAuthRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace Saloon\Http\OAuth2;

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Contracts\Body\HasBody;
use Saloon\Contracts\Authenticator;
use Saloon\Traits\Body\HasFormBody;
use Saloon\Helpers\OAuth2\OAuthConfig;
use Saloon\Traits\Plugins\AcceptsJson;
use Saloon\Http\Auth\BasicAuthenticator;

class GetClientCredentialsTokenBasicAuthRequest extends Request implements HasBody
{
use HasFormBody;
use AcceptsJson;

/**
* Define the method that the request will use.
*/
protected Method $method = Method::POST;

/**
* Define the endpoint for the request.
*/
public function resolveEndpoint(): string
{
return $this->oauthConfig->getTokenEndpoint();
}

/**
* Requires the authorization code and OAuth 2 config.
*
* @param array<string> $scopes
*/
public function __construct(protected OAuthConfig $oauthConfig, protected array $scopes = [], protected string $scopeSeparator = ' ')
{
//
}

/**
* Register the default data.
*
* @return array{
* grant_type: string,
* scope: string,
* }
*/
public function defaultBody(): array
{
return [
'grant_type' => 'client_credentials',
'scope' => implode($this->scopeSeparator, array_merge($this->oauthConfig->getDefaultScopes(), $this->scopes)),
];
}

/**
* Default authenticator used.
*/
protected function defaultAuth(): ?Authenticator
{
return new BasicAuthenticator($this->oauthConfig->getClientId(), $this->oauthConfig->getClientSecret());
}
}
22 changes: 22 additions & 0 deletions src/Traits/OAuth2/ClientCredentialsBasicAuthGrant.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Saloon\Traits\OAuth2;

use Saloon\Http\Request;
use Saloon\Helpers\OAuth2\OAuthConfig;
use Saloon\Http\OAuth2\GetClientCredentialsTokenBasicAuthRequest;

trait ClientCredentialsBasicAuthGrant
{
use ClientCredentialsGrant;

/**
* Resolve the access token request
*/
protected function resolveAccessTokenRequest(OAuthConfig $oauthConfig, array $scopes = [], string $scopeSeparator = ' '): Request
{
return new GetClientCredentialsTokenBasicAuthRequest($oauthConfig, $scopes, $scopeSeparator);
}
}
28 changes: 28 additions & 0 deletions tests/Feature/Oauth2/ClientCredentialsFlowConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Saloon\Exceptions\OAuthConfigValidationException;
use Saloon\Tests\Fixtures\Connectors\ClientCredentialsConnector;
use Saloon\Tests\Fixtures\Connectors\NoConfigClientCredentialsConnector;
use Saloon\Tests\Fixtures\Connectors\ClientCredentialsBasicAuthConnector;
use Saloon\Tests\Fixtures\Connectors\CustomRequestClientCredentialsConnector;
use Saloon\Tests\Fixtures\Requests\OAuth\CustomClientCredentialsAccessTokenRequest;

Expand Down Expand Up @@ -196,3 +197,30 @@

expect($accessTokenResponse->getRequest())->toBeInstanceOf(CustomClientCredentialsAccessTokenRequest::class);
});

test('the client credentials grant can use basic auth', function () {
$mockClient = new MockClient([
MockResponse::make(['access_token' => 'access', 'expires_in' => 3600], 200),
]);

$connector = new ClientCredentialsBasicAuthConnector;
$connector->withMockClient($mockClient);

$authenticator = $connector->getAccessToken();

expect($authenticator)->toBeInstanceOf(AccessTokenAuthenticator::class);
expect($authenticator->getAccessToken())->toEqual('access');
expect($authenticator->getRefreshToken())->toBeNull();
expect($authenticator->isRefreshable())->toBeFalse();
expect($authenticator->getExpiresAt())->toBeInstanceOf(DateTimeImmutable::class);

$mockClient->assertSentCount(1);

expect($mockClient->getLastPendingRequest()->body()->all())->toEqual([
'grant_type' => 'client_credentials',
'scope' => '',
]);

expect($mockClient->getLastPendingRequest()->headers()->get('Authorization'))
->toEqual('Basic ' . base64_encode('client-id:client-secret'));
});
32 changes: 32 additions & 0 deletions tests/Fixtures/Connectors/ClientCredentialsBasicAuthConnector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Saloon\Tests\Fixtures\Connectors;

use Saloon\Http\Connector;
use Saloon\Helpers\OAuth2\OAuthConfig;
use Saloon\Traits\OAuth2\ClientCredentialsBasicAuthGrant;

class ClientCredentialsBasicAuthConnector extends Connector
{
use ClientCredentialsBasicAuthGrant;

/**
* Define the base URL.
*/
public function resolveBaseUrl(): string
{
return 'https://oauth.saloon.dev';
}

/**
* Define default Oauth config.
*/
protected function defaultOauthConfig(): OAuthConfig
{
return OAuthConfig::make()
->setClientId('client-id')
->setClientSecret('client-secret');
}
}

0 comments on commit 74b7f67

Please sign in to comment.