dormilich/http-oauth
PSR-compatible OAuth2 Client Credentials extension for dormilich/http-client. Automatically fetches and caches access tokens via a token client/provider, then adds Authorization headers to outgoing requests. Works with PSR-18/17 HTTP clients and PSR-16 cache.
composer require dormilich/http-oauth dormilich/http-client
guzzlehttp/guzzle):
composer require guzzlehttp/guzzle
symfony/cache):
composer require symfony/cache
config/services.php:
'oauth' => [
'client_id' => env('OAUTH_CLIENT_ID'),
'client_secret' => env('OAUTH_CLIENT_SECRET'),
'token_url' => env('OAUTH_TOKEN_URL'),
],
use Dormilich\HttpClient\Client;
use Dormilich\HttpOauth\TokenClient;
use Dormilich\HttpOauth\TokenProvider;
use Dormilich\HttpOauth\Credentials\ClientCredentials;
use Dormilich\HttpOauth\Credentials\DefaultProvider;
use Dormilich\HttpOauth\Encoder\AuthorisationEncoder;
use Symfony\Component\HttpClient\Psr18Client;
use Symfony\Component\Cache\Simple\FilesystemCache;
use Symfony\Contracts\HttpClient\HttpClientInterface;
// Laravel Service Provider
public function register()
{
$this->app->singleton(HttpClientInterface::class, function ($app) {
$httpClient = new Psr18Client();
$requestFactory = new \Nyholm\Psr7\Factory\Psr17Factory();
$streamFactory = new \Nyholm\Psr7\Factory\Psr17Factory();
$cache = new FilesystemCache();
$credentials = new ClientCredentials(
config('services.oauth.client_id'),
config('services.oauth.client_secret'),
config('services.oauth.token_url')
);
$provider = new DefaultProvider($credentials);
$tokenClient = new TokenClient($provider, $httpClient, $requestFactory, $streamFactory);
$tokenProvider = new TokenProvider($tokenClient, $cache);
$authorisation = new AuthorisationEncoder($tokenProvider);
$client = new Client($httpClient, $requestFactory, $streamFactory);
$client->addEncoder($authorisation);
return $client;
});
}
use Illuminate\Support\Facades\Http;
$response = Http::client()->get('https://api.example.com/protected-endpoint');
Use ChainProvider to manage credentials for multiple OAuth providers:
$provider1 = new DefaultProvider(new ClientCredentials(
'id1', 'secret1', 'https://provider1.com/oauth/token'
));
$provider2 = new DomainProvider();
$provider2->add(new ClientCredentials(
'id2', 'secret2', 'https://provider2.com/oauth/token'
), ['api.example.com']);
$chain = new ChainProvider([$provider1, $provider2]);
$tokenClient = new TokenClient($chain, $httpClient, $requestFactory, $streamFactory);
Leverage automatic token refresh on 401/403 responses:
// Configure the TokenProvider to handle stale tokens
$tokenProvider = new TokenProvider($tokenClient, $cache, 300); // 5-minute cache TTL
Wrap the OAuth client in Laravel middleware for global protection:
// app/Http/Middleware/OAuthMiddleware.php
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->getStatusCode() === 401) {
// Trigger token refresh via the client
$this->app->make(\Dormilich\HttpOauth\TokenProvider::class)->refresh();
return $next($request);
}
return $response;
}
Use DomainProvider for environment-specific OAuth flows:
$domainProvider = new DomainProvider();
$domainProvider->add($stagingCredentials, ['staging.example.com']);
$domainProvider->add($productionCredentials, ['api.example.com']);
Override the TokenProvider in tests:
// tests/TestCase.php
protected function getMockTokenProvider()
{
$mockToken = [
'access_token' => 'mock_token_123',
'expires_in' => 3600,
];
$cache = new \Symfony\Component\Cache\Adapter\ArrayAdapter();
$cache->set('oauth_token', $mockToken, new \DateInterval('PT1H'));
return new \Dormilich\HttpOauth\TokenProvider(
$this->createMock(TokenClient::class),
$cache
);
}
expires_in in Response: If the OAuth provider omits expires_in, the package assumes the token is non-expiring but may still fail silently. Solution: Configure a default TTL in TokenProvider:
$tokenProvider = new TokenProvider($tokenClient, $cache, 300); // Force 5-minute TTL
Bearer <token>) matches the API’s expectations.$cache->delete('oauth_token_*'); // Wildcard deletion (if supported)
DomainProvider) before the fallback (DefaultProvider).api.example.com vs. *.example.com).$httpClient = new Psr18Client([
'handler' => HandlerStack::create([
new \GuzzleHttp\Middleware::tap(function ($request) {
\Log::debug('OAuth Request:', [
'url' => (string) $request->getUri(),
'headers' => $request->getHeaders(),
]);
}),
]),
]);
$tokenClient->getToken()->then(function ($response) {
\Log::debug('Token Response:', $response->getBody());
});
TokenProvider to use a database or Redis:
class DatabaseTokenProvider implements TokenProviderInterface {
public function getToken(): string { /* Query DB */ }
public function refresh(): void { /* Update DB */ }
}
AuthorisationEncoder to modify the Authorization header format:
class CustomAuthorisationEncoder extends AuthorisationEncoder {
protected function getHeaderValue(): string {
return 'Custom ' . parent::getHeaderValue();
}
}
TokenClient to handle redirects:
$tokenClient = new TokenClient($provider, $httpClient, $requestFactory, $streamFactory, [
'allow_redirects' => true,
]);
$this->app->singleton('oauth.client', function ($app) {
return (new Client($app->make(HttpClientInterface::class), ...))->addEncoder(...);
});
.env for sensitive credentials:
OAUTH_CLIENT_ID=your_client_id
OAUTH_CLIENT_SECRET=your_secret
OAUTH_TOKEN_URL=https://oauth.example.com/token
throttle middleware to avoid hitting API rate limits:
Route::middleware(['throttle:60,1'])->group(function () {
// OAuth-protected routes
});
| Error | Cause | Solution |
|---|---|---|
How can I help you explore Laravel packages today?