Logaholic.de

Avatar

web development

How to add transparent SoapHeader authentication as decorator to any existing service class

This should not be limited to it, but I’d like to show how I implemented this inside the Zend Framework and its Zend_Soap_* components.

SoapHeader authentication is a widely used method to secure access to soap Webservices where the credentials are in the request header.

Activating this type of authentication is for me down to a change from this:

$soap = new Zend_Soap_Server($uri.'&wsdl', $serverOptions);
$soap->setClass('My_Service_'.$serviceName);

to this:

$soap = new Zend_Soap_Server($uri.'&wsdl', $serverOptions);
$soap->setClass('My_Soap_Decorator_Secure', 'My_Service_'.$serviceName);

This is the basic decorator code:

/**
 * This class decorates Soap service classes, provides and enforces authentication via soap header 'authenticate'
 *
 * @author Karsten Deubert <karsten@deubert.net>
 */
class My_Soap_Decorator_Secure
{
    /**
     * @var bool
     */
    protected $_authenticationHeaderPresent = false;

    /**
     * @var mixed
     */
    protected $_authenticatedUser = null;

    /**
     * @var mixed
     */
    protected $_serviceClass = null;

    public function __construct($class)
    {
        if (!class_exists($class))
        {
            throw new Exception('invalid class: '.$class);
        }
        $this->_serviceClass = new $class();
    }

    /**
     * @param mixed $data
     * @return void
     */
    public function authenticate($data)
    {
        $this->_authenticationHeaderPresent = true;

        // authentication code which checks if credentials are valid

        $this->_authenticatedUser = $yourAuthenticatedUser;
    }

    public function __call($name, $arguments)
    {
        if (!$this->isAuthenticationHeaderPresent() || is_null($this->_authenticatedUser))
        {
            throw new Exception('authentication failed');
        }
        if (!is_callable(array($this->_serviceClass, $name)))
        {
            throw new Exception('invalid service class method');
        }

        return call_user_func_array(array($this->_serviceClass, $name), $arguments);
    }
}

The usual soap request with authentication header should now look like this:

$authData = new stdClass();
$authData->user = 'foo';
$authData->secret = 'bar';

$authHeader = new SoapHeader($namespace, 'authenticate', $authData);

$soapClient = new SoapClient('http://foo.bar/asdf?wsdl',
    array(
        'cache_wsdl' => 0,
        'soap_version' => SOAP_1_1
    )
);
$soapClient->__setSoapHeaders(array($authHeader));
$soapClient->fooMethod();

With this header the soap server will first execute the authenticate method from the decorator, then (if successful) pass the method call via magic __call to the inner service class and its fooMethod() in this example.

Voila, transparent SoapHeader authentication separated from your service classes ;)

What I haven’t researched fully yet is if there is a way to specify the authenticate header in the WSDL – every comment appreciated.

PHP 5.3.1 and SoapHeader on windows getting ignored

Today I spent a decent amount of time to realise that PHP 5.3.1 and its SoapServer doesn’t handle provided SoapHeaders as it should. If you provide a SoapHeader named ‘authenticate’ the corresponding method ‘authenticate’ should get executed before any other method – but seems to be ignored with the specified version (on windows using XAMPP).

After upgrading to PHP 5.3.5 (as in the latest XAMPP package) everything works as expected.

,