soap-apiauthenticationsecuritytutorial

SOAP Authentication: Basic Auth, WS-Security UsernameToken, and Custom Headers

SOAPless Team4 min read

SOAP authentication is usually harder than REST authentication. In a REST API, you often send one Authorization header or one API key and move on. In SOAP, authentication can happen at the HTTP layer, inside the SOAP header, or both. The WSDL may describe part of it, or almost none of it.

In practice, most teams run into one of these three cases:

  • HTTP Basic Auth at the transport layer
  • WS-Security UsernameToken in the SOAP header
  • A provider-specific custom header

That is what this guide focuses on.

Method 1: HTTP Basic Authentication

Some SOAP services use standard HTTP Basic Auth instead of SOAP-specific security. It is the simplest option, but it still matters because teams often waste time assuming everything must be WS-Security.

curl -u "myuser:mypassword" \
  -H "Content-Type: text/xml" \
  -d @request.xml \
  "https://api.example.com/Service"

In Node.js:

const credentials = Buffer.from("myuser:mypassword").toString("base64");

const response = await fetch("https://api.example.com/Service", {
  method: "POST",
  headers: {
    "Content-Type": "text/xml;charset=UTF-8",
    Authorization: `Basic ${credentials}`,
    SOAPAction: '"http://example.com/GetUser"',
  },
  body: soapEnvelope,
});

How to recognize it:

  • you get 401 Unauthorized
  • the response includes WWW-Authenticate: Basic
  • the integration guide says "Basic Auth" even if the WSDL says little

Basic Auth is simple, but it still assumes HTTPS. Base64 is not protection.

Method 2: WS-Security UsernameToken

WS-Security (Web Services Security) is the standard authentication mechanism for SOAP services. The most common profile is UsernameToken, which embeds credentials directly in the SOAP header.

Plain Text Password

The simplest WS-Security variant sends the username and password in plain text within the SOAP header:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsse:UsernameToken>
        <wsse:Username>myuser</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
          mypassword
        </wsse:Password>
      </wsse:UsernameToken>
    </wsse:Security>
  </soap:Header>
  <soap:Body>
    <!-- Your request here -->
  </soap:Body>
</soap:Envelope>

Important: Plain text WS-Security credentials must always be sent over HTTPS. Without TLS, the password is visible to anyone intercepting the traffic.

Password Digest (Nonce-Based)

For higher security, the PasswordDigest profile hashes the password with a nonce (random value) and a timestamp. The server never sees the raw password — it verifies the digest using its stored copy.

<wsse:UsernameToken>
  <wsse:Username>myuser</wsse:Username>
  <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">
    <!-- Base64(SHA-1(Nonce + Created + Password)) -->
    kYe4gEOP9FwUBGHExPnGcHtaink=
  </wsse:Password>
  <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">
    tKUH8ab3Rokm4t6IAlgx0Q==
  </wsse:Nonce>
  <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    2026-03-15T10:00:00Z
  </wsu:Created>
</wsse:UsernameToken>

Generating the digest in Node.js:

import crypto from "crypto";

function createPasswordDigest(password, nonce, created) {
  const nonceBuffer = Buffer.from(nonce, "base64");
  const hash = crypto.createHash("sha1");
  hash.update(Buffer.concat([
    nonceBuffer,
    Buffer.from(created, "utf-8"),
    Buffer.from(password, "utf-8"),
  ]));
  return hash.digest("base64");
}

const nonce = crypto.randomBytes(16).toString("base64");
const created = new Date().toISOString();
const digest = createPasswordDigest("mypassword", nonce, created);

Common pitfall: The Created timestamp usually has to be inside a narrow time window. If your server clock is off, the request can fail even when the username and password are correct.

This is also where teams misread auth errors. Sometimes the credentials are fine and the real issue is a timestamp window, namespace mismatch, or the wrong SOAP 1.1 / 1.2 binding.

Method 3: Custom SOAP Headers

Some services implement their own authentication schemes using custom SOAP headers. This is common with services that predate WS-Security or have proprietary security requirements.

<soap:Header>
  <auth:Credentials xmlns:auth="http://example.com/auth">
    <auth:ApiKey>your-api-key-here</auth:ApiKey>
    <auth:Signature>hmac-sha256-signature</auth:Signature>
    <auth:Timestamp>2026-03-15T10:00:00Z</auth:Timestamp>
  </auth:Credentials>
</soap:Header>

There's no standard for custom headers. This is the "the provider gave us a PDF and one sample XML" category, so namespace URIs, element ordering, and timestamp/signature rules all matter.

Figuring Out Which Method You Need

When documentation is sparse, use these signals to determine the required authentication method:

  1. Check the WSDL policy section first. Look for <wsp:Policy> entries such as <sp:UsernameToken> or <sp:HttpBasicAuthentication>.

  2. Send an unauthenticated request and examine the error. The error response often hints at the expected method:

    • 401 with WWW-Authenticate: Basic → HTTP Basic Auth
    • SOAP fault mentioning wsse:Security → WS-Security
    • A SOAP fault mentioning a missing custom auth header → Usually a provider-specific SOAP header
  3. Ask the provider. SOAP services often have separate integration guides that describe authentication requirements more clearly than the WSDL.

Simplifying SOAP Authentication

SOAP authentication is expensive in a way REST teams often underestimate. WS-Security means precise XML, timestamp handling, and namespace correctness. Even Basic Auth is annoying once multiple apps and scripts all need to reproduce the same integration.

That is where a managed boundary helps. With SOAPless, you can store upstream SOAP credentials once, keep the SOAP-specific authentication logic in one place, and expose a simpler REST JSON interface to downstream teams. Today that maps best to:

  • HTTP Basic Auth
  • WS-Security UsernameToken
  • Custom outbound headers

If your upstream requires something heavier, such as client certificates or an organization-wide SAML flow, treat that as a separate integration project. It is usually not just “one more header.”