phpsoapheadersoapserversoapfault

PHP SoapServer addSoapHeader ignored with SoapFault


I have written a PHP SOAP Service that accepts Basic Authentication credentials as outlined at http://www.whitemesa.com/soapauth.html. I did this by defining a method BasicAuth inside the handler class of the SOAPServer instance. This all works fine.

However, when authentication fails for some reason (incorrect username, no BasicAuth header in the request) I'd like to include a BasicChallenge header in my response, like this:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header>
      <h:BasicChallenge xmlns:h="http://soap-authentication.org/basic/2001/10/"
                        SOAP-ENV:mustUnderstand="1">
         <Realm>Realm</Realm>
      </h:BasicChallenge>
   </SOAP-ENV:Header>
   <SOAP-ENV:Body>
      <SOAP-ENV:Fault>
         <faultcode>SOAP-ENV:Client</faultcode>
         <faultstring>Authentication failed</faultstring>
      </SOAP-ENV:Fault>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The following code does not work (the header is not added to the response).

$soapServer->addSoapHeader(new SoapHeader("http://soap-authentication.org/basic/2001/10/", "BasicChallenge", array("Realm" => "Realm"), true));
throw new SoapFault("Client", "Authentication Failed");

Calling $soapServer->fault() instead of throw new SoapFault does not make a difference.

I've tried constructing the Fault object myself, and returning that as a regular response, but I was unable to get PHP to send a well-formed response.

Thanks in advance.


Solution

  • This was 2013 and I actually have exactly the same problem. Well, it 's 2019 and PHP 's current version is 7.3 and 7.4 is coming up to us with big steps. Unfortunately the SoapServer class ignores soap headers completely in a SoapFault case.

    I 've written a small workaround to manipulate the XML response of the soap server. For everone who 's having the same issue, here 's a small example, how to solve it.

    1. Initialise your Soap Server

    $server = new SoapServer($wsdl, $options);
    
    ob_start();
    $server->setObject($service);
    $server->handle();
    $response = ob_get_contents();
    ob_end_clean();
    

    Actually we 're doing a simple initialization. Instead of simple returning the content we intercept the xml response with the output buffering functions. The result of the is a xml string in $response.

    2. Find out if the reponse is a fault

    Actually the SoapServer class is adding soap headers, if it was a valid response. We have to find out, if the response is a fault.

    $doc = new DOMDocument();
    $doc->loadXML($response);
    
    $xpath = new DOMXPath($doc);
    $isFault = $xpath->query('//*[local-name()="Fault"]')->length;
    

    Just load the response xml string into the DOMDocument class. From now on we are able to access xml elements with DOM functions. For better handling I 'm using the DOMXPath class. Of course it is also possible to determine the XML nodes with the DOMDocument class. The $isFault variable is useful for the next step.

    3. In fault case set a soap header

    Unfortunately there is no fancy addSoapHeader function for simply setting a soap header. We have to do it manually in this case.

    if ($isFault) {
        $header = $doc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'Header');
        $body = $doc->getElementsByTagNameNS('http://schemas.xmlsoap.org/soap/envelope/', 'Body')->item(0);
    
        $realm = $doc->createElementNS('http://soap-authentication.org/basic/2001/10/', 'h:Realm');
        $basicChallenge = $doc->createElementNS('http://soap-authentication.org/basic/2001/10/', 'h:BasicChallenge');
    
        $basicChallenge->appendChild($realm);
        $header->appendChild($basicChallenge);
    
        $doc->documentElement->insertBefore($header, $body);
    }
    
    $xmlResponse = $doc->saveXML($doc->documentElement);
    
    header('Content-Length: ' . strlen($xmlResponse));
    echo $xmlResponse;
    exit();
    

    In a fault case we create a header element and add all the child nodes we need. If averything was appended insert the header before the body, save the new xml structure in a string and echo it.

    Hope this helps a bit.