Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Guzzle Wsse Middleware Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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.

Implementation Patterns

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.

Gotchas and Tips

  • Abandoned and low-adoption (31 stars, 0 dependents, last release 2019). The middleware is roughly a hundred lines. If Composer conflicts with Guzzle 7/PHP 8, copy the class into app/Support/Http/ and maintain it yourself. That is usually safer than pinning an old Guzzle.
  • Nonce encoding is the classic bug. The digest must be 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.
  • Clock skew. The server enforces a window around 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').
  • Digest vs. plaintext. Some servers are configured for 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.
  • Header name is not universal. The package emits 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.
  • Do not log the header wholesale. The digest plus nonce and timestamp enables offline password guessing. Log the username and a request ID; redact X-WSSE in Guzzle's message logging.
  • Scope the middleware. Pushing WSSE onto a globally shared handler leaks credentials to unrelated hosts. Use per-service clients or gate the middleware on the request host.
  • Config caching. Resolve credentials through config('services.*'), never env() inside the provider or middleware.
  • Extension points are minimal. For SHA-256 digests, custom nonce lengths, or per-request credentials, write your own middleware callable — you already know the exact header format from the package source.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings