SOAP Security Tutorial - WS-Security, body credentials and client certificates
UsernameToken, X.509 signing, SAML, body credentials and mutual TLS - each configured from the Dashboard with no code changes.
Production SOAP endpoints authenticate in three different places - the SOAP header, the message body and the TLS layer - and they often demand more than one at once. This tutorial configures each mechanism from the Dashboard and shows that the service code never changes: security is a property of the connection, not of your Python.
The running example continues from the calling tutorial - an immunization registry that wants the submitter's username and password inside the message body, over TLS. Along the way you will also set up UsernameToken, message signing and SAML, which is what other ecosystems require of the same connections.
In this tutorial
- Create a WS-Security definition
- Attach it to a connection
- Inject credentials into the body
- Sign messages with X.509
- Add SAML assertions
- Present a client certificate
- Deploy with enmasse
Remember: you can connect your AI copilot to Zato documentation.
Create a WS-Security definition
A WS-Security definition holds the credentials and crypto material once - connections then reference it by name. One definition can serve many connections.
Step 1. In the Dashboard, navigate to Security > WS-Security, click Create a new WS-Security definition and fill in the form:
- Name:
Registry Credentials - Username:
my-registry-user - Mode:
Username token - Click OK
Step 2. The definition is created with a random password - click Change password in its row and set the real one. Passwords are never part of the create or edit forms, so they never linger in browser autofill or form history.
The mode decides what the definition puts on the wire:
- Username token - a
wsse:UsernameTokenheader with the username and password; the Username token tab has a Password digest switch for endpoints that require digest form with a nonce and timestamp - X.509 - signing and encryption, configured in the Crypto material tab
- SAML - a SAML 2.0 assertion built from the fields in the SAML tab
Every field has a How does it work? link explaining what it does.
Attach it to a connection
Open your outgoing SOAP connection (Connections > Outgoing > SOAP), switch to the Security tab and pick WS-Security/Registry Credentials from the security dropdown. Click OK.
From this moment every invoke through this connection includes the WS-Security header. The service code is exactly what it was before there was any security:
request = SOAPMessage()
request.namespace = 'urn:cdc:iisb:2011'
request.echoBack = 'Hello registry'
response = self.soap['Immunization Registry'].invoke('connectivityTest', request)
Inject credentials into the body
Immunization registries following the CDC WSDL do not read the SOAP header at all - they expect the username and password as the first elements of the business message:
<sub:submitSingleMessage>
<sub:username>my-registry-user</sub:username>
<sub:password>*******</sub:password>
<sub:hl7Message>MSH|^~\&amp;|...</sub:hl7Message>
</sub:submitSingleMessage>
Step 1. Open the connection and switch to the Body credentials tab.
Step 2. Add two rows:
- Element name:
username - Element name:
password
Rows without a position become the operation's first children in row order - which is exactly the layout above. An endpoint that wants a credential elsewhere gets a 1-based Position on its row.
Step 3. Click OK.
The values come from the WS-Security definition attached to the connection, and injection happens inside invoke, after your service built the message. The service never sees, holds or logs the credentials:
# Only business fields here - username and password are injected
# by the connection, per its body-credential mapping.
request = SOAPMessage()
request.namespace = 'urn:cdc:iisb:2011'
request.hl7Message = hl7_payload
response = self.soap['Registry Submit'].invoke('submitSingleMessage', request)
Sign messages with X.509
For endpoints that verify message integrity, switch the definition's Mode to X.509 and open the Crypto material tab:
- Sign - turn it on
- Signing key - your private key, PEM
- Signing certificate chain - your certificate (and any intermediates), PEM, sent along so the receiver can verify the signature
To also encrypt for the receiver:
- Encrypt - turn it on
- Peer certificate - the receiver's certificate, PEM
For the return direction, the Decryption key decrypts what comes back encrypted to you and Trust anchors holds the CA certificates that peers' signatures are validated against.
The signature and encryption are standard XML-DSig and XML-Encryption, applied to each outgoing envelope when invoke runs - once more, no service changes.
Add SAML assertions
Healthcare document exchanges (IHE XUA, TEFCA) identify the requesting user with a SAML 2.0 assertion in the WS-Security header, signed by the sending organization.
Switch the definition's Mode to SAML and fill in the SAML tab:
- Issuer - who vouches for the assertion, e.g.
https://my-hospital.example.com - Subject - the user or system the assertion is about
- Audience - who the assertion is meant for, e.g. the exchange's community identifier
To sign the assertion - which these profiles require - turn on Sign in the Crypto material tab and provide the Signing key and Signing certificate chain, the same fields the X.509 mode uses. The assertion is generated fresh for each request and includes an enveloped RSA-SHA256 signature.
Present a client certificate
Endpoints that authenticate at the TLS layer need the connection itself to present a certificate. That is a connection-level setting, so it combines with everything above.
Open the connection's Security tab and fill in:
- Client certificate - a local path, e.g.
/opt/zato/certs/client-cert.pem - Client key - a local path, e.g.
/opt/zato/certs/client-key.pem
The paths point to PEM files on the server. In containerized deployments, mount the certificates into the container and put the mount paths in these fields. If the certificate and key live in one combined PEM file, fill in only the certificate field.
A connection can now do all three at once - present the client certificate, inject body credentials and sign its messages - which is precisely the combination some registries and national networks require.
Deploy with enmasse
The complete setup - definition and connection - in YAML:
security:
- name: Registry Credentials
username: my-registry-user
password: Zato_Enmasse_Env.RegistryPassword
type: wss
mode: username_token
outgoing_soap:
- name: Registry Submit
host: https://registry.example.gov
url_path: /iisb/services
security: Registry Credentials
soap_action: urn:cdc:iisb:2011:submitSingleMessage
soap_version: "1.2"
timeout: 30
tls_client_cert: /opt/zato/certs/client-cert.pem
tls_client_key: /opt/zato/certs/client-key.pem
body_credentials:
- name: username
- name: password
The Zato_Enmasse_Env. prefix reads the password from an environment variable, so no secret ever lands in the repository.
The other two modes are YAML too. An X.509 definition holds its switches and PEM material inline:
security:
- name: Registry Signing
username: my-registry-user
type: wss
mode: x509
sign: true
encrypt: true
signing_key: |-
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
signing_certificate_chain: |-
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
decryption_key: |-
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
peer_certificate: |-
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
trust_anchors: |-
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
And a SAML definition holds the assertion fields, plus the signing material when the assertion is to be signed:
security:
- name: Exchange Assertion
username: my-registry-user
type: wss
mode: saml
issuer: https://my-hospital.example.com
subject: CN=Dr Smith,O=Example Hospital
audience: urn:exchange:community
sign: true
signing_key: |-
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
signing_certificate_chain: |-
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
Import it in the Dashboard under System → Config → Import enmasse, or mount the file under /opt/hot-deploy/enmasse/enmasse.yaml inside the container to have it imported on start.
Every field of all three modes is documented in the enmasse reference.
What you built
- A WS-Security definition holding credentials and crypto material once, referenced by name
- UsernameToken authentication with optional password digest
- Body-credential mapping - username and password injected into the message body by the connection, invisible to service code
- X.509 signing and encryption and signed SAML assertions, switched on per definition
- Mutual TLS through client certificate paths on the connection
- Enmasse deployment with secrets kept in environment variables
For the reference documentation of every mechanism, read SOAP security.
Schedule a meaningful demo
Book a demo with an expert who will help you build meaningful systems that match your ambitions
"For me, Zato Source is the only technology partner to help with operational improvements."
- John Adams