WS-Security: authentication, signing and encryption
Message-level security protects the payload through intermediaries, which is exactly what TLS cannot do once a gateway terminates the connection.
UsernameToken and timestamps
<soap:Header xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp wsu:Id="TS-1">
<wsu:Created>2026-09-18T10:00:00Z</wsu:Created>
<wsu:Expires>2026-09-18T10:05:00Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken wsu:Id="UT-1">
<wsse:Username>svc-orders</wsse:Username>
<!-- Digest form, never a plain-text Password element -->
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">Q2hhbmdlTWU=</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">bG9uZ25vbmNl</wsse:Nonce>
<wsu:Created>2026-09-18T10:00:00Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>- The digest is base64 of SHA1 over nonce + created + password, so the password itself never travels.
- A timestamp with an expiry is what makes replay impractical. A server that ignores the timestamp accepts a captured token forever.
- Digest still leaves the message readable, so WS-Security is usually combined with TLS rather than replacing it.
- Server clocks must be close. A few minutes of skew is the usual allowance, and more than that is a security downgrade.
Signing and encryption
<!-- A signature over the body, referenced by wsu:Id -->
<soap:Body wsu:Id="BODY-1" xmlns:wsu="...">
<PlaceOrder xmlns="urn:example:orders"><sku>A-1</sku><qty>2</qty></PlaceOrder>
</soap:Body>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#BODY-1">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>...</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>...</ds:SignatureValue>
<ds:KeyInfo><ds:SecurityTokenReference>...</ds:SecurityTokenReference></ds:KeyInfo>
</ds:Signature>| Threat | Control | Notes |
|---|---|---|
| Eavesdropping | TLS, plus XML Encryption for the payload | Encryption survives a gateway that terminates TLS |
| Tampering in transit | Signature over the body | Canonicalisation must match on both sides |
| Replay | Timestamp plus nonce cache | Cache nonces for at least the timestamp window |
| Credential theft | Client certificate or SAML assertion instead of a shared password | Keys in an HSM or key store, never in config |
| Repudiation | Long-lived signature with a verifiable chain | Keep the signed original, not a re-serialised copy |
⚠️
XML signature wrapping is the classic WS-Security attack: an attacker adds a second element with the same ID so the signature verifies against one copy while the application reads another. Defend by rejecting duplicate IDs in the message and by validating the signed part before any business logic runs.
FAQ
Is WS-Security still needed if everything is over HTTPS?
It is needed whenever the message must stay protected beyond the TLS endpoint, for example through an ESB or a store-and-forward queue, or when a partner requires an XML signature for non-repudiation.
Why do signatures fail between Java and .NET?
Almost always canonicalisation or namespace prefix differences producing a different byte sequence. Pin the exclusive canonicalisation algorithm on both sides and test with a fixed fixture.
Related
SOAP headers and the WS-* standards SOAP in the enterprise: ESBs, gateways and middleware
Last refreshed 2026-09-18.