guzzlehttp/oauth-subscriber
Guzzle middleware that signs HTTP requests with OAuth 1.0. Compatible with Guzzle 7.10+ and PHP 7.2.5+. Configure consumer/token secrets once on a HandlerStack, then enable per request (auth=oauth) or globally, with optional per-request token override.
Install the Package
Add to composer.json:
"require": {
"guzzlehttp/oauth-subscriber": "^0.9"
}
Run composer update.
Configure OAuth Credentials
Store credentials in .env (e.g., for Twitter API):
OAUTH_CONSUMER_KEY=your_key
OAUTH_CONSUMER_SECRET=your_secret
OAUTH_TOKEN=your_token
OAUTH_TOKEN_SECRET=your_token_secret
Create a Guzzle Client with OAuth Middleware
In a service class (e.g., app/Services/OAuthClient.php):
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
class OAuthClient
{
public function __construct()
{
$stack = HandlerStack::create();
$stack->push(new Oauth1([
'consumer_key' => config('oauth.consumer_key'),
'consumer_secret' => config('oauth.consumer_secret'),
'token' => config('oauth.token'),
'token_secret' => config('oauth.token_secret'),
]));
$this->client = new Client([
'base_uri' => config('oauth.base_uri'),
'handler' => $stack,
'auth' => 'oauth', // Enable OAuth for all requests
]);
}
public function getClient(): Client
{
return $this->client;
}
}
Register the Service in AppServiceProvider
Bind the client to Laravel’s container:
public function register()
{
$this->app->singleton(OAuthClient::class, function ($app) {
return new OAuthClient();
});
}
First Use Case: Fetch Data Inject the client into a controller or service:
use App\Services\OAuthClient;
class TwitterController extends Controller
{
public function __construct(private OAuthClient $oauthClient)
{
}
public function homeTimeline()
{
$response = $this->oauthClient->getClient()->get('statuses/home_timeline.json');
return response()->json($response->getBody());
}
}
config/oauth.php:
return [
'consumer_key' => env('OAUTH_CONSUMER_KEY'),
'consumer_secret' => env('OAUTH_CONSUMER_SECRET'),
'token' => env('OAUTH_TOKEN'),
'token_secret' => env('OAUTH_TOKEN_SECRET'),
'base_uri' => env('OAUTH_BASE_URI', 'https://api.twitter.com/1.1/'),
];
config() helper to access values.Override token/secret per request (e.g., for multi-account APIs):
$response = $client->get('endpoint', [
'auth' => 'oauth',
'oauth' => [
'token' => 'temp_token',
'token_secret' => 'temp_secret',
],
]);
For APIs requiring RSA (e.g., some enterprise OAuth providers):
$stack->push(new Oauth1([
'consumer_key' => config('oauth.consumer_key'),
'consumer_secret' => config('oauth.consumer_secret'),
'private_key_file' => storage_path('oauth/private_key.pem'),
'private_key_passphrase' => env('OAUTH_PRIVATE_KEY_PASSPHRASE'),
'signature_method' => Oauth1::SIGNATURE_METHOD_RSA,
]));
Use middleware to refresh tokens on failure (e.g., expired tokens):
$client->get('endpoint', [
'on_stats' => function (TransferStats $stats) {
if ($stats->getHandler()->getLastTransferWasSuccessful() === false) {
// Refresh token logic here (e.g., call a token refresh endpoint)
// Rebuild the client with new credentials
}
},
]);
Use Laravel’s HTTP testing tools to mock OAuth responses:
$response = $this->get('/api/twitter/timeline')
->assertStatus(200)
->assertJsonStructure(['data' => []]);
Inspect signed requests for debugging:
$client->get('endpoint', [
'debug' => true,
'on_request' => function (RequestInterface $request) {
\Log::debug('OAuth Headers:', $request->getHeaders());
},
]);
Nonce Entropy Issues
/dev/urandom on Linux).$stack->push(new Oauth1([
'nonce' => 'custom_nonce_' . time(),
// ... other config
]));
Duplicate Query Parameters
Oauth1::SIGNATURE_METHOD_HMAC_SHA256 (more forgiving).Private Key File Permissions
chmod 600 storage/oauth/private_key.pem
Token Secret Not Required for 2-Legged OAuth
token_secret for 2-legged OAuth (no user token):
$stack->push(new Oauth1([
'consumer_key' => 'key',
'consumer_secret' => 'secret',
'token' => '', // Empty for 2-legged
'token_secret' => '', // Empty for 2-legged
]));
Middleware Order Matters
Oauth1 last in the stack to ensure it signs the final request.Logging Secrets
oauth request options can expose secrets.\Log::debug('Request URL:', $request->getUri());
Inspect Raw Headers Use Guzzle’s debug handler to see signed requests:
$client = new Client([
'handler' => HandlerStack::create([
new \GuzzleHttp\Handler\CurlHandler(),
new \GuzzleHttp\Middleware::tap(function (RequestInterface $request) {
\Log::debug('Request Headers:', $request->getHeaders());
}),
]),
]);
Validate Signatures Manually Use tools like OAuth 1.0 Playground to test your credentials and signature method.
Check for oauth_signature in Payload
oauth_signature, the package will throw an error (fixed in v0.8.2).Timezone Mismatches
date_default_timezone_set('UTC');
Custom Signature Methods
Extend Oauth1 to support non-standard methods (e.g., PLAINTEXT for testing):
class CustomOauth1 extends Oauth1
{
public const SIGNATURE_METHOD_PLAINTEXT = 'PLAINTEXT';
protected function getSignatureMethod(): string
{
return self::SIGNATURE_METHOD_PLAINTEXT;
}
protected function getSignature(string $baseString, string $key): string
{
return $key . '&' . $baseString; // PLAINTEXT example
}
}
Nonce Generation Hook Override nonce logic for custom entropy sources:
$stack->push(new Oauth1([
'nonce' => function () {
return bin2hex(random_bytes(
How can I help you explore Laravel packages today?