I’m a little bit out of my comfort zone. I’m trying to create a SOAP request with PHP, and I’m using the famous API of KBO, here’s the documentation:
So essentially everything went well, but I’m still struggling with something about the authentication, which makes me fail my entire small framework:
<?php
$endpoint = 'https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub';
$wsdl = 'https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub?wsdl';
$username = 'myusername';
$password = 'mypassword';
$timestamp = gmdate('Y-m-dTH:i:sZ');
$nonce = base64_encode(random_bytes(16));
$passwordDigest = base64_encode(sha1($nonce . $timestamp . $password, true));
$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>' . $username . '</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . $passwordDigest . '</wsse:Password>
<wsse:Nonce>' . $nonce . '</wsse:Nonce>
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">' . $timestamp . '</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>';
$options = array(
'soap_version' => SOAP_1_1,
'trace' => true,
'exceptions' => true,
'encoding' => 'UTF-8',
'cache_wsdl' => WSDL_CACHE_NONE,
'stream_context' => stream_context_create(array(
'http' => array(
'header' => 'Authorization: WSSE profile=' . $header,
'user_agent' => 'PHPSoapClient'
),
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false
)
))
);
$context = stream_context_create(array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
),
));
$options['stream_context'] = $context;
// $options = array(
// 'soap_version' => SOAP_1_1
// );
$client = new SoapClient($wsdl, $options);
// Set up the request parameters
$enterpriseNumber = '0810.002.854';
$request = array('EnterpriseNumber' => $enterpriseNumber);
// Call the SOAP operation with the request parameters
$response = $client->__soapCall('ReadEnterprise', array($request));
// Get the request and response XML
$requestXML = $client->__getLastRequest();
$responseXML = $client->__getLastResponse();
var_dump($response);
I also followed some suggestions from my PHP log file, where it was written to use a different version of the soap version: SOAP_1_1 instead of the SOAP_1_2.
What I find really challenging is the fact that I can’t debug it in any way; I have no idea on how I can check the error message, or I can’t have any hint in my error log file, it simply mentions the security check, but nothing much.
Has anyone had the same issue?
By the way, the error that I have encountered is the following:
[14-Feb-2023 17:09:24 UTC] PHP Fatal error: Uncaught SoapFault exception: [ns1:SecurityError] A security error was encountered when verifying the message in
Stack trace:
#0 /Users/mymac/Sites/cboxform/api-call/index.php(55): SoapClient->__soapCall(‘ReadEnterprise’, Array)
#1 {main}
thrown in /Users/mymac/Sites/cboxform/api-call/index.php on line 55
TLTR:
To summarize, I would need a request that looks like:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mes="http://economie.fgov.be/kbopub/webservices/v1/messages" xmlns:dat="http://economie.fgov.be/kbopub/webservices/v1/datamodel">
<soapenv:Header>
<mes:RequestContext>
<mes:Id>myid</mes:Id>
<mes:Language>fr</mes:Language>
</mes:RequestContext>
</soapenv:Header>
<soapenv:Body>
<mes:ReadEnterpriseRequest>
<dat:EnterpriseNumber>0206231995</dat:EnterpriseNumber>
</mes:ReadEnterpriseRequest>
</soapenv:Body>
</soapenv:Envelope>
with a header with a digest password, with timestamp and nonce that will expire in 300 seconds to this endpoint:
https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub?wsdl
with also a username.
2
Answers
It looks like an issue with the security token that you’re using to authenticate your SOAP request.
Please check
timezone
format as UTC(YYYY-MM-DDTHH:MM:SSZ)
,Nonce
should be a random value that is generated for each request, andPasswordDigest
value is calculated correctly,PasswordDigest
(SHA-1 hash) and concatenated values forNonce
, Createdtimestamp
,password
in binary format.Also you can try to print out the values of the
timestamp
,nonce
, andpasswordDigest
for easy to debuggingCan you try replace at line 20 within below code:
WSSE header to use the correct profile value within space after it
The KBO Public Webservice uses
ns1:SecurityError
as a catch-all exception, which makes it hard to determine the actual cause.As a first step, I have found it working:
This typically works when the language code and enterprise number have been set.
But what is the actual payload?
log.info mockRequest.requestContent
.Note that a password digest can only be used ONCE. On subsequent use it will also return the catch-all ns1:SecurityError. A sample of a working SQL-implementation is (shown on https://forums.invantive.com/t/uitlezen-kbo-public-search-api-geeft-foutmelding-ns1-securityerror/3424/2):