Update:
More recent Blog article








Soap Authentication for Yii

You use the WebService for Yii and want users to authenticate before they use other commands? Then read further..

Prerequisits

You should have set up some basic authentication for your website already and just want to enhance it with a webservice. Authentication works similar to this.

Implementation

Our goal is to have a soapclient which works like this:
	
$client = new SoapClient('http://127.0.0.1/yii/test/api/soap/wsdl');  
// login using username and password
$session = $client->login('testuser@example.com', 'secretPassword');
// calling some other soap functions using our retrieved session key
$client->furtherMethod($session, 123);
$client->furtherMethod2($session, 'abc');
$client->furtherMethod3($session);
	
So you call the login command, get a sessionkey and all further requests use this sessionkey.

What the controller should look like:
	
Yii::import('user.components.UserIdentity');
class SoapController extends Controller
{
    /** 
     * @param string the username
     * @param string the password
     * @return string a sessionkey
     * @soap
     */
    public function login($name, $password)
    {   
        $identity = new UserIdentity($name, $password);
        $identity->authenticate();
        if ($identity->errorCode == UserIdentity::ERROR_NONE)
			Yii::app()->user->login($identity, 3600);
		else
			throw new SoapFault("login", "Problem with login");
        $sessionKey = sha1(mt_rand());
        Yii::app()->cache->set('soap_sessionkey'.$sessionKey.Yii::app()->user->id, $name.':'.$password, 1800);
        return $sessionKey;
    }

	/**
	 * authenticates a user via the sessionid
	 * throws an exception on error
	 */
    protected function authenticateBySession($sessionKey)
    {  
        $data = Yii::app()->cache->get('soap_sessionkey'.$sessionKey.Yii::app()->user->id);
		list($name, $password) = explode(':', $data);
        if ($name)
        {  
            $identity = new UserIdentity($name, $password);
            $identity->authenticate();
            if ($identity->errorCode == UserIdentity::ERROR_NONE)
                Yii::app()->user->login($identity, 3600);
        }
        // happens when session is invalid or login not possible (deleted, deactivated)
        if (!Yii::app()->user->id)
            throw new SoapFault('authentication', 'Your session is invalid');
    }

    /** 
     * @param string the session key
     * @param int random stuff
     * @return int current user id
     * @soap
     */
    public function furtherMethod($session, $bla)
	{
		$this->authenticateBySession($session);
		return Yii:.app()->user->id;
	}
}
	
This is the basic idea behind this. But I wouldn't recommend exactly this way because we store the plaintext password inside the cache.


Security improvement

My solution for this was to only store the username and don't validate the password on further requests.
	
protected function authenticateBySession($sessionKey)
{  
	$name = Yii::app()->cache->get('soap_sessionkey'.$sessionKey.Yii::app()->user->id);
	if ($name)
	{  
		$identity = new UserIdentity($name, '');
		$identity->authenticate(true);
		...
	
Inside the UserIdentity model:
	
public function authenticate($passwordless = false)
{ 
	$user=User::model()->find('LOWER(email)=?',array(strtolower($this->username)));
	if($user && !$passwordless && !$user->validatePassword($this->password))
		// throw password is wrong error
	...
	
I kept it a bit shorter because your UserIdentity model surely looks different than mine and everyone elses..
I hope you got the application working. If not you can write me an email and I try to help.

TODO

I don't know yet how to use https for webservices.