christianjbrown / oauth2-client
A thin, strongly-typed PHP 8.5+ OAuth 2.0 client that manages access tokens (refresh-token and client-credentials grants), caching them behind a mockable key-value store.
Package info
github.com/christianjbrown/oauth2-client-php
pkg:composer/christianjbrown/oauth2-client
Requires
- php: ^8.5
- christianjbrown/api-client: ^1.0
- christianjbrown/key-value-store: ^1.0
- psr/http-client: ^1.0
Requires (Dev)
- christianjbrown/code-quality-scripts: ^1.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^13.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-28 13:21:35 UTC
README
A small, strongly-typed PHP OAuth 2.0 client that fetches and caches access tokens. It hides the token endpoint behind a couple of token managers, caches the resulting access (and refresh) token in an interchangeable key-value store, and only calls the endpoint again when the cached token is missing, expired, or a refresh is forced.
Two grant types ship today:
- Refresh token (
RefreshTokenManager) — exchanges a stored refresh token for a new access token. - Client credentials (
ClientCredentialsTokenManager) — exchanges HTTP Basic credentials for an access token.
Both return an AccessTokenInterface and normalise transport and payload failures into a single
library exception hierarchy, so callers stay decoupled from the underlying HTTP client.
✔️ Prerequisites
💡 If you're on MacOS and have Homebrew, PHP and Composer will install with brew install composer.
🏗️ Installation
For your composer-enabled project:
composer require christianjbrown/oauth2-client
💻 Usage
Both managers are constructed with a JSON API request sender (from
api-client), one or more key-value
stores for the cached tokens, an access-token transformer, and the token endpoint URL.
🔄 Refresh token grant
use ChristianBrown\OAuth2Client\RefreshTokenManager; use ChristianBrown\OAuth2Client\Transformer\AccessTokenTransformer; $manager = new RefreshTokenManager( $jsonApiRequestSender, // ChristianBrown\ApiClient\JsonApiRequestSenderInterface $accessTokenStore, // ChristianBrown\KeyValueStore\KeyValueStoreInterface $refreshTokenStore, // ChristianBrown\KeyValueStore\KeyValueStoreInterface new AccessTokenTransformer(), 'https://example.com/oauth/token', ); $accessToken = $manager->getAccessToken('my-client-id'); $accessToken->getAccessToken(); // the bearer token string $accessToken->getExpiresIn(); // seconds until expiry // Force a refresh even if a valid token is cached: $accessToken = $manager->getAccessToken('my-client-id', true);
The manager returns the cached access token while it is still valid. Otherwise it POSTs the stored refresh token to the endpoint, caches the new access and refresh tokens, and returns the fresh token.
🔑 Client credentials grant
use ChristianBrown\OAuth2Client\ClientCredentialsTokenManager; use ChristianBrown\OAuth2Client\Transformer\AccessTokenTransformer; $manager = new ClientCredentialsTokenManager( $jsonApiRequestSender, // ChristianBrown\ApiClient\JsonApiRequestSenderInterface $accessTokenStore, // ChristianBrown\KeyValueStore\KeyValueStoreInterface new AccessTokenTransformer(), 'https://example.com/oauth/token', ); // The Basic auth value is the raw "client_id:client_secret"; the manager base64-encodes it. $accessToken = $manager->getAccessTokenFromBasicAuth( 'my-client-id:my-client-secret', 'my-scope', // optional 'my-client-id', // optional ); $accessToken->getAccessToken();
🎫 The access token
Every manager returns a ChristianBrown\OAuth2Client\Model\AccessTokenInterface:
public function getAccessToken(): string; public function getExpiresIn(): int; public function getRefreshToken(): ?string; public function getScope(): ?string; public function getTokenType(): AccessTokenType; // enum, currently AccessTokenType::BEARER
🚨 Error handling
Everything the library throws implements
ChristianBrown\OAuth2Client\Model\Exception\ExceptionInterface (which extends Throwable):
RequestExceptionInterface— the token endpoint request failed. The originalapi-clientexception is available viagetRequestException().BadResponsePayloadFieldExceptionInterface— the endpoint responded, but a field was missing, the wrong type, or an unsupported value.getField()andgetData()expose the offending field and the full payload.
use ChristianBrown\OAuth2Client\Model\Exception\ExceptionInterface; try { $accessToken = $manager->getAccessToken('my-client-id'); } catch (ExceptionInterface $e) { print $e->getMessage(); }
📄 License
Released under the MIT License.