JaggyGauran

Freelance developer, and designer

PHP SoapClient with NTLM

For quite some time now, I've been working a lot of web service requests, specifically, SOAP requests. Which is kinda painful for me because I have major annoyances with request and response naming conventions of most of the services.

Uh-oh

There are some issues that you will encounter when trying to send requests to an NTLM authenticated web service.

Problem #1: You can't directly reference the URL of the authenticated web service.

This goes for both NTLM and (as far as I know) Basic HTTP authentication.

$client = new SoapClient('http://0.0.0.0/some/random/url.svc?singleWsdl', [
    'login'      => 'username',
    'password'   => 'password',
    'exceptions' => true
]);

Which will get you: PHP Fatal error: SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://0.0.0.0/some/random/url.svc?singleWsdl' : failed to load external entity "http://0.0.0.0/some/random/url.svc?singleWsdl" in /Users/jaggyspaghetti/code/webservice.git/app/routes.php on line 11

Now, it's kind of a bummer.

Solution: Fetch the WSDL and reference it locally.

/**
 + Fetch the wsdl and set it to the tmp folder
 *
 + @access protected
 + @return void
 */
protected function fetchWsdl($url, $credentials)
{
    $username = $credentials['username'];
    $password = $credentials['password'];
    $handle = curl_init();

    curl_setopt($handle, CURLOPT_URL, $url);
    curl_setopt($handle, CURLOPT_HEADER, false);
    curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);

    curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
    curl_setopt($handle, CURLOPT_USERPWD, "{$username}:{$password}");

    $response = curl_exec($handle);
    curl_close($handle);

    $filename    = strtotime(date('Ymd His')) . '.wsdl';
    $destination = "/tmp/{$filename}";

    file_put_contents($destination, $response);

    return $destination;
}

Take note at these lines;

curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
curl_setopt($handle, CURLOPT_USERPWD, "{$username}:{$password}");

This is basically how to authenticate via NTLM.

Problem #2: You can't use SoapClient to acccess web service Requests with NTML authentication.

$client = new SoapClient('/path/to/downloaded/wsdl', $options);
$client->ReadMultiple([]);

Again, sadly, it'll return a SoapFault: Unauthorized. It seens that even if you use the login parameters, it seems that it's only used for basic authentication. So, how to we work around this?

Solution: Extend and use cUrl!

<?php
class NtlmClient extends SoapClient
{
    protected $options;


    public function __construct($url, $options = [])
    {
        $this->options = $options; // so we can access the credentials
        parent::__construct($url, $options);
    }


    public function __doRequest($request, $location, $action, $version, $one_way = false)
    {
        $this->__last_request = $request;

        $handle = curl_init($location);

        $credentials = $this->options['login'] . ':' . $this->options['password'];
        $headers = [
            'Method: POST',
            'User-Agent: PHP-SOAP-CURL',
            'Content-Type: text/xml; charset=utf-8',
            'SOAPAction: "' . $action . '"'
        ];

        curl_setopt($handle, CURLINFO_HEADER_OUT, true);
        curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($handle, CURLOPT_POST, true);
        curl_setopt($handle, CURLOPT_POSTFIELDS, $request);
        curl_setopt($handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);

        // Authentication
        curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
        curl_setopt($handle, CURLOPT_USERPWD, $credentials);

        $response = curl_exec($handle);

        return $response;
    }
}

We extend the soap client and use curl to send our soap requesets to the server. We send our xml requests and we get our responses in the same way, stdClass

Other Solutions?

Now, I'm not sure if this is the best or most efficient way to handle NTLM requests but it's the one that kinda works for us currently.

If you guys know any other ways to access NTLM protected web services, hit me up on twitter @jaggygauran. Thanks!