SOAP API の認証は、REST API よりひと手間どころでは済まないことがよくあります。REST なら Authorization ヘッダーや API キーで終わる場面でも、SOAP では HTTP レイヤーで認証したり、SOAP Header に UsernameToken を入れたり、独自ヘッダーを再現したりします。
実務でよく出てくるのは、だいたい次の 3 つです。
- HTTP Basic Auth
- WS-Security
UsernameToken - 独自の SOAP ヘッダー
この記事では、この 3 つを中心に整理します。
認証方式の全体像
SOAP API で現実によく出てくる認証方式は、まず次の 4 つです。
| 認証方式 | 複雑度 | 使用頻度 | セキュリティ |
|---|---|---|---|
| HTTP Basic Auth | 低 | 中 | 低(HTTPS 必須) |
| WS-Security UsernameToken | 中 | 高 | 中 |
| WS-Security with Digest | 高 | 中 | 高 |
| 独自 SOAP ヘッダー | 中〜高 | 中 | 実装次第 |
方式 1: HTTP Basic Auth
最もシンプルな方法です。SOAP の仕様ではなく、HTTP レイヤーで認証します。
# curl での Basic Auth 付き SOAP 呼び出し
curl -X POST https://example.com/service \
-u "username:password" \
-H "Content-Type: text/xml; charset=utf-8" \
-H 'SOAPAction: "http://example.com/services/GetUser"' \
-d '<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:tns="http://example.com/services">
<soap:Body>
<tns:GetUserRequest>
<tns:userId>42</tns:userId>
</tns:GetUserRequest>
</soap:Body>
</soap:Envelope>'
// Node.js: node-soap で Basic Auth
const soap = require('soap');
const client = await soap.createClientAsync(wsdlUrl);
client.setSecurity(new soap.BasicAuthSecurity('username', 'password'));
const [result] = await client.GetUserAsync({ userId: 42 });
# Python: zeep で Basic Auth
from zeep import Client
from requests import Session
from requests.auth import HTTPBasicAuth
session = Session()
session.auth = HTTPBasicAuth('username', 'password')
from zeep.transports import Transport
transport = Transport(session=session)
client = Client(wsdl_url, transport=transport)
見分ける手掛かりは次のようなものです。
401 Unauthorizedが返るWWW-Authenticate: Basicが見える- 接続手順書に「Basic 認証」と書かれている
注意: Basic Auth はユーザー名とパスワードを Base64 エンコードしただけなので、必ず HTTPS で通信してください。
方式 2: WS-Security UsernameToken
SOAP らしい認証方式として一番よく出てくるのがこれです。SOAP エンベロープの Header にセキュリティトークンを入れます。
プレーンテキスト UsernameToken
最もシンプルな WS-Security の形式です。
<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"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-1">
<wsse:Username>apiuser</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">secretpassword</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">dGVzdG5vbmNl</wsse:Nonce>
<wsu:Created>2026-03-15T10:00:00.000Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<!-- オペレーションの内容 -->
</soap:Body>
</soap:Envelope>
各要素の意味は次の通りです。
- Username: API ユーザー名
- Password: パスワード(Type 属性で PasswordText または PasswordDigest を指定)
- Nonce: リプレイ攻撃防止用のランダム値(Base64 エンコード)
- Created: タイムスタンプ(サーバーがタイムスキューを検証する)
PasswordDigest 方式
PasswordText ではなく PasswordDigest を要求するサービスもあります。こちらは nonce と timestamp を含めてハッシュ化します。
PasswordDigest = Base64(SHA-1(Nonce + Created + Password))
// Node.js での PasswordDigest 計算
const crypto = require('crypto');
function createPasswordDigest(nonce, created, password) {
const nonceBuffer = Buffer.from(nonce, 'base64');
const createdBuffer = Buffer.from(created, 'utf-8');
const passwordBuffer = Buffer.from(password, 'utf-8');
const combined = Buffer.concat([nonceBuffer, createdBuffer, passwordBuffer]);
const hash = crypto.createHash('sha1').update(combined).digest('base64');
return hash;
}
const nonce = crypto.randomBytes(16).toString('base64');
const created = new Date().toISOString();
const digest = createPasswordDigest(nonce, created, 'secretpassword');
// node-soap で WS-Security を設定
const soap = require('soap');
const client = await soap.createClientAsync(wsdlUrl);
client.setSecurity(new soap.WSSecurity('apiuser', 'secretpassword', {
hasNonce: true,
hasTimeStamp: true,
passwordType: 'PasswordDigest' // または 'PasswordText'
}));
ここで詰まりやすいのは、資格情報そのものより次のような細部です。
PasswordTextとPasswordDigestを取り違えている- サーバーと時刻がずれている
- namespace や Header の形が微妙に違う
- 本当は SOAP 1.1 / 1.2 の取り違えで、認証エラーっぽく見えている
方式 3: カスタム SOAP ヘッダー認証
一部の SOAP サービスは標準の WS-Security ではなく、独自のヘッダーで認証します。
<soap:Header>
<auth:AuthHeader xmlns:auth="http://example.com/auth">
<auth:ApiKey>your-api-key-here</auth:ApiKey>
<auth:Timestamp>2026-03-15T10:00:00Z</auth:Timestamp>
<auth:Signature>HMAC-SHA256-signature</auth:Signature>
</auth:AuthHeader>
</soap:Header>
// node-soap でカスタムヘッダーを追加
const client = await soap.createClientAsync(wsdlUrl);
client.addSoapHeader({
'auth:AuthHeader': {
'auth:ApiKey': 'your-api-key-here',
'auth:Timestamp': new Date().toISOString(),
'auth:Signature': computeHmacSignature(apiKey, timestamp, secret)
},
attributes: {
'xmlns:auth': 'http://example.com/auth'
}
});
このカテゴリは標準化されていないので、結局はベンダーの資料とサンプルに忠実に合わせるしかありません。
WSDL から認証要件を読み取る
WSDL には WS-Policy セクションで認証要件が記述されていることがあります。
<wsp:Policy wsu:Id="SecurityPolicy">
<sp:SupportingTokens>
<wsp:Policy>
<sp:UsernameToken
sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:HashPassword/> <!-- PasswordDigest を要求 -->
</wsp:Policy>
</sp:UsernameToken>
</wsp:Policy>
</sp:SupportingTokens>
</wsp:Policy>
確認ポイント:
<sp:UsernameToken>があれば WS-Security UsernameToken が必要<sp:HashPassword/>があれば PasswordDigest が必須<sp:TransportBinding>があれば HTTPS 前提- WSDL に情報が薄い場合は、別紙の接続手順書やベンダー資料を探す
認証のデバッグ
認証エラーが発生した場合は、以下の順序で確認すると切り分けやすくなります。
- 認証方式そのもの: Basic Auth なのか WS-Security なのかを取り違えていないか
- 認証情報の正確性: ユーザー名やパスワードの前後に空白や改行が入っていないか
- タイムスタンプのスキュー: Created のタイムスタンプがサーバー時刻と大きくずれていないか
- Header の形: Nonce や namespace URI、要素の並びが期待通りか
- 周辺条件: そもそも SOAP version や endpoint がずれていないか
認証の複雑さから解放される方法
SOAP の認証がつらいのは、1 回つながれば終わりではないからです。認証方式の理解、XML ヘッダーの再現、タイムスタンプや Nonce の扱い、障害時の切り分けが、呼び出し元ごとに何度も発生します。
SOAPless が相性よく使えるのは、こうした認証の面倒を 1 か所に集約したい時です。現在の機能としては、特に次のような接続先の SOAP と噛み合います。
- HTTP Basic Auth
- WS-Security
UsernameToken - カスタム送信ヘッダー
接続先の SOAP 認証を 1 回設定しておけば、利用側のチームは API キー付きの REST JSON エンドポイントを前提に開発できます。SOAP Header を各アプリやスクリプトが毎回組み立てなくて済む、というのが一番大きい価値です。