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

Wse Php Laravel Package

robrichards/wse-php

robrichards/wse-php is a PHP library that adds WS-Security to SOAP requests, offering XML signing, encryption and decryption, security tokens and timestamp support for secure, interoperable web service integrations.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and requirements

composer require robrichards/wse-php
  • Requires ext-soap, ext-dom, ext-openssl, and (for current versions) robrichards/xmlseclibs as a transitive dependency.
  • Namespace is RobRichards\WsePhp (not RobRichards\WsePhp\... legacy names). If you're porting old blog snippets, they often use the pre-2.0 class names — check the version you installed.
  • Verify with php -m | grep -E 'soap|dom|openssl' before debugging anything else; a missing ext-soap produces confusing "class not found" errors.

Where to look first

  • src/WSSESoap.php — the class you'll spend 90% of your time with: addTimestamp(), addUserToken(), addBinaryToken(), signSoapDoc(), encryptSoapDoc(), saveXML().
  • src/WSSESoapServer.php — inbound side: processHeaders(), validateTimestamp(), validateUserToken(), verifySignature(), decryptSoapDoc().
  • src/WSASoap.php — WS-Addressing headers (addAction(), addTo(), addMessageID(), addReplyTo()).
  • examples/ — the repo's client/server/encryption samples are the fastest reference for correct call order.

First use case: a signed outbound request

Most integrations fail because a vendor requires a wsse:Security header with a Timestamp, a UsernameToken, and a signature over the body. The canonical pattern is overriding SoapClient::__doRequest(), because ext/soap gives you no hook to mutate the outgoing envelope:

use RobRichards\WsePhp\WSSESoap;
use RobRichards\XMLSecLibs\XMLSecurityKey;

class SignedSoapClient extends \SoapClient
{
    public function __construct($wsdl, array $options, private string $cert, private string $key, private string $pass) {
        parent::__construct($wsdl, $options);
    }

    public function __doRequest($request, $location, $action, $version, $oneWay = false): ?string
    {
        $doc = new DOMDocument();
        $doc->preserveWhiteSpace = true;
        $doc->loadXML($request);

        $wsse = new WSSESoap($doc);
        $wsse->addTimestamp(300);                 // valid 5 minutes
        $wsse->addUserToken($this->user, $this->pass); // usually non-digest for legacy vendors

        $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
        $key->loadKey($this->key, true, $this->pass);
        $wsse->signSoapDoc($key);                 // must be the LAST mutation

        return parent::__doRequest($wsse->saveXML(), $location, $action, $version, $oneWay);
    }
}

If you get InvalidSecurity, Security header missing, or Signature verification failed from a partner on the first attempt, that's normal — proceed to the gotchas section before changing code.

Implementation Patterns

Wrap it in a service class, not in the controller

Inject config (cert path, key path, passphrase, endpoint, credentials) from config/services.php, backed by env vars. Keep the SoapClient subclass thin and construct one instance per request/tenant. Laravel's container can bind a factory that resolves the right certificate per tenant or environment.

// config/services.php
'acme' => [
    'wsdl'       => env('ACME_WSDL'),
    'cert'       => storage_path('app/certs/acme.crt'),
    'key'        => storage_path('app/certs/acme.key'),
    'passphrase' => env('ACME_KEY_PASSPHRASE'),
    'username'   => env('ACME_USER'),
    'password'   => env('ACME_PASS'),
],

Store certs in storage/app/certs, not in the repo; set 0600 permissions; never let a passphrase leak into logs.

Sign-then-encrypt for confidential payloads

Typical enterprise ordering: signature over body, timestamp, and username token, then encrypt the body only:

$wsse->addTimestamp(300);
$wsse->addUserToken($user, $pass);
$wsse->signSoapDoc($privateKey);

$recipientKey = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, ['type' => 'public']);
$recipientKey->loadKey($theirCertPem, false);
$wsse->encryptSoapDoc($recipientKey, [
    'encrypt_signature' => false,
    'encrypt_certificate' => false,
    'algorithm' => XMLSecurityKey::AES256_CBC,
]);

Don't encrypt the signature unless the receiver explicitly expects a signed-and-encrypted Security header; some stacks (older WCF, SAP PI) reject it.

Inbound verification

For a partner calling you, read raw XML from php://input (do not let ext/soap deserialize first) and process:

$server = new \RobRichards\WsePhp\WSSESoapServer(file_get_contents('php://input'));
if (!$server->processHeaders()) { abort(400, 'Malformed WS-Security'); }
if (!$server->validateTimestamp()) { abort(400, 'Stale timestamp'); }
// verifySignature() against the partner's uploaded cert / or the embedded BinarySecurityToken
$server->verifySignature($trustedCertPath);
$xml = $server->saveXML();

Then hand the verified XML to your own parser or a local SoapServer with SoapServer::setClass(). Keep processHeaders() as the first operation; it populates the internal state everything else depends on.

WS-Addressing

Some endpoints (BizTalk, older Oracle SOA) require wsa:Action, wsa:MessageID, and wsa:To. Use WSASoap on the same DOMDocument before signing — the addressing headers become part of the signed set if your signature covers all headers.

$wsa = new WSASoap($doc);
$wsa->addAction('urn:SubmitOrder');
$wsa->addTo($endpoint);
$wsa->addMessageID(\Ramsey\Uuid\Uuid::uuid4()->toString());
$doc = $wsa->saveXML(); // re-load into WSSESoap

Testing

  • Unit-test envelope shape by asserting with DOMXPath + the wsse/ds namespaces: exactly one Timestamp, one UsernameToken, one Signature, Reference URI="#..." pointing at the body wsu:Id.
  • Integration-test against SoapUI/Postman mock or the vendor sandbox; signature validation cannot be meaningfully mocked.
  • For deterministic tests, freeze the clock (Carbon setTestNow) so timestamp assertions don't flake.

Retries and idempotency

Never reuse a signed envelope. Each retry must rebuild timestamp, nonce (for PasswordDigest), and signature. Cache the WSDL (soap.wsdl_cache) but not the signed payload.

Gotchas and Tips

Sign last — always. addTimestamp(), addUserToken(), addBinaryToken(), and WSASoap mutations must precede signSoapDoc(). Touching the DOM afterward (even formatOutput = true) invalidates canonicalization and the receiver rejects the signature with a generic failure.

Canonicalization is fragile. Load the raw request with preserveWhiteSpace = true and don't reformat, pretty-print, or round-trip through SimpleXML. If the vendor uses inclusive C14N instead of exclusive, you'll need to switch canonicalization method explicitly; mismatched C14N is the #1 cause of "signature invalid" on an otherwise correct envelope.

__getLastRequest() lies. It returns the envelope ext/soap built, before your __doRequest() override rewrote it. Log the string inside __doRequest() (before and after signing) to see what actually went on the wire. This trips up nearly everyone debugging signature failures.

__doRequest() signature changed in PHP 8.1+. Match the parent exactly (: ?string, bool $oneWay = false) or you'll get a fatal "declaration must be compatible" error after a PHP upgrade.

BinarySecurityToken format. addBinaryToken() expects a certificate it can parse; PEM headers must be stripped or converted to DER internally. If your vendor sends X509SubjectKeyIdentifier references instead of BinarySecurityToken, the default signing path won't match their expectation — check which wsse:Reference value/ValueType they require.

PasswordDigest vs PasswordText. addUserToken($u, $p, true) produces a digest (nonce + created + password, base64 SHA-1). Legacy .NET and SAP endpoints often want plain text, and some want digest with a specific nonce encoding. When auth fails but the signature verifies, this flag is usually the culprit.

Timestamps and skew. addTimestamp(300) = 5 minutes of validity; servers with clock drift reject at 60 seconds. Also, the wsu:Created value must be UTC ISO-8601 (2024-01-01T00:00:00Z), not local time — PHP's default timezone will silently break this.

Certificate/key mismatches. XMLSecurityKey::loadKey() throws opaque OpenSSL errors if the passphrase is wrong or the key is encrypted in an unsupported format. Validate at boot: openssl_x509_read(file_get_contents($cert)) and openssl_pkey_get_private(...). On the encryption side, you can encrypt to a public cert but you cannot decrypt without the matching private key — a common source of "works in test, fails in prod" when test uses a self-signed pair.

Header ordering. Some stacks require wsse:Security to be the first child of soap:Header. If so, pass ['insertBefore' => true] (or reorder after construction, before signing) — but reordering after signing breaks the signature.

Server-side processing mutates the document. processHeaders() rewrites $this->doc (strips the Security header, decrypts the body). Call saveXML() immediately after and don't hold a reference to the original XML string expecting it to match.

SOAP 1.1 vs 1.2. Namespace constants differ (NS_SOAP vs NS_SOAP12); the library detects them from the envelope, but your SoapClient options (soap_version) and the vendor's expectations must agree. Mismatched versions produce signature references that resolve to nothing.

No MTOM/SwA support. Attachments must be base64-inlined. If the partner requires MTOM, this library is the wrong tool — you'll need a lower-level implementation.

Extension points. Subclass WSSESoap to customize header assembly or to add proprietary tokens; override WSSESoapServer::processHeaders() to support custom token types or to hook audit logging. For signature reference manipulation (multiple body parts, wsu:Id naming), drop down to XMLSecurityDSig from xmlseclibs directly and use WSSESoap's constants for namespaces.

Debug with external tools. xmlsec1 --verify --pubkey-cert-pem cert.pem signed.xml will tell you definitively whether your signature is valid, removing the partner from the loop. SoapUI's WS-Security configuration is a good second opinion on header shape.

Pin the version and read the changelog. The API has shifted across major versions (namespaces, signSoapDoc() signature, encryption options). A composer update in a working integration is a genuine risk; pin with ~3.0 (or whatever your installed major is) and test upgrades against the vendor sandbox.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console