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

Psr18 Wsse Middleware Laravel Package

php-soap/psr18-wsse-middleware

PSR-18 middleware that adds WS-Security UsernameToken authentication to outgoing SOAP requests. Drop it into your HTTP client stack to secure envelopes with username/password credentials—framework-agnostic and easy to combine with php-soap transports.

Deep Wiki
Context7

Getting Started

php-soap/psr18-wsse-middleware is a small, focused add-on for php-soap/psr18-transport. It decorates a PSR-18 client so that the outgoing SOAP envelope is augmented with a WS-Security (<wsse:Security>) header — most commonly a UsernameToken (plain or digest) or an X.509 signature — before the request hits the wire. If you're talking to a legacy enterprise endpoint (SAP, WCF/.NET, Oracle, government SOAP APIs) that rejects unsecured requests, this is the missing piece.

Install

composer require php-soap/psr18-transport php-soap/psr18-wsse-middleware
# plus any PSR-18 client, e.g.:
composer require guzzlehttp/guzzle

You'll need ext-dom, ext-libxml and ext-openssl available (the package relies on robrichards/xmlseclibs under the hood).

First use case: UsernameToken in front of a legacy endpoint

use PhpSoap\Psr18Transport\Psr18Transport;
use PhpSoap\Psr18WsseMiddleware\WsseMiddleware;
use PhpSoap\Psr18WsseMiddleware\WSSecurity\UsernameToken;

$transport = Psr18Transport::createWithDefaultClient()
    ->withMiddleware(
        WsseMiddleware::create(
            UsernameToken::create($username, $password) // digest + nonce + created by default
        )
    );

Every SOAP call made through that transport now carries the security header. The middleware only touches the request body — it is PSR-18 client agnostic, so Guzzle, Symfony HttpClient, or php-http/discovery all work identically.

Where to look first

  • README.md for the two supported security flavours (UsernameToken, X.509 signature) and their factory signatures — these have shifted slightly across releases.
  • src/WsseMiddleware.php for the PSR-18 middleware entry point and how it rewrites the request body.
  • src/WSSecurity/ for the token/header value objects. This is where to confirm whether you have UsernameToken::create(), fromCredentials(), or a constructor call in your pinned version.
  • test/ for fixture envelopes — the fastest way to see a correctly signed request before you point it at a live server.

Implementation Patterns

Pick the token mode deliberately. Digest (PasswordDigest) is the default expectation for most enterprise stacks and avoids sending the password in clear text; PasswordText is only acceptable over TLS and is often requested by older Oracle/SAP endpoints. Confirm which one the server expects before debugging signatures — the failure modes look almost identical ("invalid security").

Apply it at the transport, not per call. Build one configured transport in a service provider and reuse it:

// app/Providers/SoapServiceProvider.php
$this->app->singleton('billing-soap-transport', function () {
    return Psr18Transport::createWithDefaultClient()
        ->withMiddleware(
            WsseMiddleware::create(
                UsernameToken::create(
                    config('services.billing.username'),
                    config('services.billing.password'),
                )
            )
        );
});

This keeps credentials out of call sites, ensures every request is signed, and lets Laravel's config/env handling (and secret managers like Vault or AWS Secrets Manager) be the single source of truth.

Compose with the rest of your middleware stack. Typical order:

  1. Correlation ID / tracing header
  2. Logging (redacted — never log the password or the digest value)
  3. WSSE signing (last, so it sees the final body)

If you have a retry middleware, signing must happen inside the retry loop, because the nonce/created timestamp and the digest are per-attempt. Reusing a signed body after a retry is a common cause of "message expired" / replay rejections.

X.509 signing pattern. When the contract calls for XML signature rather than a username token, the middleware is configured with a certificate and private key instead of credentials. Keep the key as a mounted file or a secret, not a repo file; the middleware hands it to xmlseclibs, which is unforgiving about PEM vs DER, passphrases, and key permissions. The server often requires specific parts to be signed (Body, Timestamp, sometimes UsernameToken), so read the WSDL/WS-Policy first.

Multi-tenant / multi-credential. Build one transport per credential set and resolve it per tenant — do not mutate a shared transport's middleware with fresh credentials at request time. Small package, no thread-safety guarantees, and it's easy to leak one tenant's credentials into another's request in a queue worker.

Testing pattern. Inject a fake PSR-18 client that captures the RequestInterface, then assert against the raw XML: the wsse:Security header exists, the digest matches the expected base64(sha1(nonce + created + password)), and the correct namespaces are present. For signature mode, verify with xmlseclibs in the test rather than diffing strings — c14n output is fragile to compare literally.

Gotchas and Tips

Clock skew is the number one failure. UsernameToken digest includes a wsu:Created timestamp; servers typically reject anything more than ±5 minutes off. Run NTP on app and queue hosts, and check the middleware's TTL configuration if the library exposes one — a request that sits in a queue for ten minutes before dispatch will fail.

PSR-7 streams are single-use. The middleware has to read the request body and replace it with a new stream. If any middleware before it consumes the body without rewinding, signing will operate on an empty string and you'll get a valid-looking but empty signature. Put WSSE first in the stack, or ensure earlier middleware calls $stream->rewind().

Content-Length goes stale. Signing changes the body size. Some PSR-18 clients recalculate automatically, some happily send the old value and the server truncates the envelope. If you see puzzling parse errors on the remote side, dump the request with the computed length and compare.

Canonicalization and namespace prefixes. XML signatures are computed over c14n output, which is sensitive to inherited namespace declarations on the SOAP envelope. If you inject the security header in a way that changes prefixes on <soap:Body>, the signature will verify locally but fail on the server. Generate the envelope with the transport, never hand-build it around the middleware.

SOAP 1.1 vs SOAP 1.2. The envelope namespace differs (http://schemas.xmlsoap.org/soap/envelope/ vs http://www.w3.org/2003/05/soap-envelope), and so do the expected Content-Type and SOAPAction handling. Signing the wrong envelope shape produces the same "security header not found" style errors as missing credentials.

Don't confuse WSSE with the rest of the WS- stack.* This package adds security only. WS-Addressing, MTOM attachments, and WS-Policy negotiation are separate concerns and need their own middleware or manual headers.

Errors surface as exceptions, not SOAP faults. Signing failures (bad key, unreadable certificate, malformed token) throw from the middleware or xmlseclibs before the request leaves the process. Wrap transport construction in a boot-time check — fail fast on missing certificate paths rather than on the first customer request.

Debug by capturing the signed request, not the response. Add a temporary logging middleware after WSSE that writes the raw XML to a file, then replay it against the server with curl. You can also paste the envelope into a WSSE validator or inspect it with xmlseclibs directly. Remove or redact the logging before production — the header contains credentials or a signature.

Pin and read the changelog. The package is small and pre-1.0-ish in spirit; class names and factory methods have moved between releases. Pin a minor version in composer.json and skim the diff when upgrading — a silent rename in WSSecurity\ is more likely than a behavioural change.

Extension point for non-standard tokens. If the server wants SAML assertions, Kerberos tickets, or a proprietary header, don't fork the package. Implement your own PSR-18 middleware that produces the <wsse:Security> fragment and register it in the same transport stack, or decorate WsseMiddleware and post-process the signed request.

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