eightpoints/guzzle-wsse-middleware
Guzzle middleware that adds WS-Security (WSSE) UsernameToken authentication to outgoing requests. It generates the nonce, created timestamp and password digest automatically, so SOAP and other WSSE-protected APIs can be called through Guzzle with minimal setup.
eightpoints/guzzle-wsse-middleware is a tiny Guzzle middleware that adds a WS-Security UsernameToken header (X-WSSE) to every outgoing request. It is aimed at legacy enterprise/SOAP/WCF-style APIs that reject plain basic auth and expect a UsernameToken with a nonce, created timestamp, and password digest.
Install it and inspect the constraints before writing any code:
composer require eightpoints/guzzle-wsse-middleware
composer show eightpoints/guzzle-wsse-middleware
The last release was in 2019, so check the guzzlehttp/guzzle and php constraints in composer.json — this package predates Guzzle 7 and PHP 8. If Composer refuses to install alongside your Laravel version, treat the source as reference code rather than a dependency (see Gotchas).
The public surface is deliberately small. The usual entry point is:
use EightPoints\Guzzle\WsseAuthMiddleware;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
$stack = HandlerStack::create();
$stack->push(WsseAuthMiddleware::middleware(
config('services.legacy.username'),
config('services.legacy.password')
));
$client = new Client([
'handler' => $stack,
'base_uri' => config('services.legacy.base_uri'),
]);
$response = $client->post('/orders', ['json' => $payload]);
A plugin wrapper (WsseAuthPlugin) is also provided for pushing the middleware onto an existing handler stack per request. Read src/WsseAuthMiddleware.php first — it is small enough to audit in a few minutes, and you should confirm exactly how it builds the nonce, timestamp, and PasswordDigest.
Your first real use case is almost always a single external service with credentials in config/services.php:
// config/services.php
'legacy' => [
'base_uri' => env('LEGACY_BASE_URI'),
'username' => env('LEGACY_USERNAME'),
'password' => env('LEGACY_PASSWORD'),
],
Then bind a configured client as a singleton in AppServiceProvider. Do not resolve credentials from env() outside config — config caching will break it.
Singleton client per service. Bind one GuzzleHttp\Client per WSSE-protected service and inject it. Keeping the HandlerStack in the container means the middleware runs on every request without repeating setup:
$this->app->singleton('legacy.client', function () {
$stack = HandlerStack::create();
$stack->push(WsseAuthMiddleware::middleware(
config('services.legacy.username'),
config('services.legacy.password')
));
return new Client([
'handler' => $stack,
'base_uri' => config('services.legacy.base_uri'),
'timeout' => 10,
]);
});
Thin service wrapper. Wrap the client in a LegacyApi class that exposes domain methods (placeOrder, getShipment). This keeps WSSE mechanics out of controllers and gives you one place to add logging, retries, and exception translation.
Laravel HTTP client interop. The Http facade is Guzzle underneath but does not expose the handler stack fluently. You can still reuse a prepared stack:
$stack = HandlerStack::create();
$stack->push(WsseAuthMiddleware::middleware($user, $pass));
$response = Http::withOptions(['handler' => $stack])
->post('https://legacy.example.com/orders', $payload);
Prefer the bound Guzzle client for anything long-lived; a fresh stack per call is fine for occasional calls but makes connection reuse and retries harder to reason about.
Testing with Guzzle's MockHandler. Push the history middleware before the WSSE middleware so the recorded request includes the header:
$container = [];
$mock = new MockHandler([new Response(200, [], 'ok')]);
$stack = HandlerStack::create($mock);
$stack->push(Middleware::history($container));
$stack->push(WsseAuthMiddleware::middleware('user', 'secret'));
(new Client(['handler' => $stack]))->get('https://example.test/');
$header = $container[0]['request']->getHeaderLine('X-WSSE');
$this->assertStringContainsString('Username="user"', $header);
Queue jobs and retries. Because the middleware generates the nonce inside __invoke, each send through the stack gets a fresh token. If you use RetryMiddleware, push it after the WSSE middleware so the retry re-enters the WSSE layer and regenerates the nonce. Replaying an identical Created/Nonce pair will trip server-side replay protection.
Multiple services. Use named clients or a small factory keyed by service name; do not push two WSSE middlewares onto one stack, or the last one wins.
app/Support/Http/ and maintain it yourself. That is usually safer than pinning an old Guzzle.base64(sha1($rawNonce . $created . $password, true)), while the header's Nonce is base64($rawNonce). Using the base64 string inside the digest produces a valid-looking header that the server rejects. Verify the package's implementation and your test.Created (often 5 minutes). Keep NTP running on app servers; a drifted container produces intermittent 401s that look like bad credentials. Created should be UTC, typically gmdate('Y-m-d\TH:i:s\Z').PasswordText rather than PasswordDigest. If auth fails with a correct password, check the server's policy before debugging your hash. The middleware may expose a digest toggle.X-WSSE. Some SOAP stacks expect Authorization: WSSE profile="UsernameToken" alongside it, or a custom header. If so, subclass the middleware or write a ten-line replacement rather than fighting it.X-WSSE in Guzzle's message logging.config('services.*'), never env() inside the provider or middleware.How can I help you explore Laravel packages today?