Saturday, June 21, 2008

Zend_Auth and secure cookies

I will again talk about cookies, but this time, with a pinch of Zend_Auth.

Previously, I introduced a class to manage secure cookies. Now, we will see how to use it in a very common case : authentication with Zend_Auth.

Let us remind the main lines of Zend_Auth :

If you're like me and you don't like to use sessions, you may think that using sessions only to store users identities (a simple integer or a short string in most common cases) is a waste. Sessions are heavy and hard to manage when application run in a webserver farm.

What do you think about storing user identity on the client side ? You may think it's dangerous... and you're right ! We can already think about worst disastrous stories :

Server> Hi ! Who are you ? I can't see your face.
Malicious client> I'm the website administrator ! Look at me ! I've got... i've got a lot of good words to prove it to you !
Server> Hi Boss ! Sorry i didn't recognized you ! Here are the keys of your realm.

Generally, public don't like this kind of stories (especially your users). So we will use some cryptographic tricks to prevent malicious users from faking their identities. We will store the identity in a secure cookie (using BigOrNot_CookieManager class).

I wrote a class (implementing Zend_Auth_Storage_Interface) to store users identities in a secure cookie.

You can download it here (BigOrNot_CookieManager class is also in the archive).

Here is how to use it :

$cookieManager = new BigOrNot_CookieManager('SECRET_KEY');
$authStorage = new BigOrNot_Auth_Storage_Cookie($cookieManager);
$auth = Zend_Auth::getInstance();
$auth->setStorage($authStorage);

[...]

By defaut, the cookie's name is "auth" and default parameters are passed to setcookie().

If you want to modify these parameters, you can pass a second parameter to BigOrNot_Auth_Storage_Cookie's constructor : an array (or a Zend_Config instance).

Supported options are : cookieName, cookieExpire, cookiePath, cookieDomain, cookieSecure, cookieHttpOnly. (Names are self explanatory, see setcookie()'s documentation if you have a doubt).

Example :

$cookieManager = new BigOrNot_CookieManager('SECRET_KEY');

$storageConfig = array(
    'cookieName' => 'BigOrNauth',
    'cookieExpire' => (time() + 3600),
    'cookiePath' => '/',
    'cookieDomain' => 'bigornot-fr.blogspot.com'
);

$authStorage = new BigOrNot_Auth_Storage_Cookie($cookieManager, $storageConfig);
$auth = Zend_Auth::getInstance();
$auth->setStorage($authStorage);

Identity is stored "serialized" so you can store every serializable data you want. Avoid big objects ! Big objects = big cookies. Don't forget that they're transmitted with all request.

Identity is stored encrypted, so you will not give any sensitive information using this technique

Note :
As we saw in the previous post, BigOrNot_CookieManager::setcookie() method need a "username" parameter (or any unique user identifier)
In BigOrNot_Auth_Storage_Cookie's case, i use a md5 hash of the serialized identity.

Enjoy !

5 comments:

Anonymous said...

I have been looking for help in getting Zend_Auth to work with secure cookies and your post was the first one that looked promising. Then I realized that the link to your sample code was broken. Could you post your sample code in your blog or provide a working link? Many thanks.

Mat said...

Sorry, the link is ok now :)

Anonymous said...

Thanks! I have another question. When I look at the write method of your implementation of the Zend_Auth_Storage_Interface it looks like you are handling a mixed variable. The problem that I run into is when Zend_Auth calls authenticate it tries to pass the identity value which is a single value, but my cookie encryption class is expecting an array of several values. That's what I want to store, but the behavior of authenticate is not letting me do this. I noticed that you seem to expect more than the identify value, but your write statement only saves the identity. This does not match what I would expect. Can you offer more insight into what is happening with your solution? Any help is appreciated.

Mat said...

I don't know if I understand your problem correctly. You want to pass an array to your cookie encryption class ? What informations does the array contains ?
I see two solutions :

1) All informations in the array are specific to the authentication. In this case, your Zend_Auth_Adapter_X::authenticate() function must build this array and return it in a Zend_Auth_Result object. Zend_Auth_Result handle mixed variable for the identity, so the array won't be a problem.

Then, you can write your own Zend_Auth_Storage_X class. The write() method will just have to forward the identity array to your cookie encryption class.


2) The array contain the identity + some generic params.
In this case, all the work can be done in your Zend_Auth_Storage_X class.

Pass your generic params to the constructor, and store them in your class.

In the write() function, build the array (based on the identity you received + the generic params you have stored in your class) and pass it to your cookie encryption class.



With my class, it work like the second solution : "generic params" are passed to the constructor of my Zend_Auth_Storage class.
When calling write() function, the identity is serialized and my cookie encryption class is called with the serialized identity and the generics params.
Because of the serialization of the identity value, you can't use it as a config array for your cookie encryption class.


Hope it'll help you. If I don't understand your problem, please post an example code and your cookie encryption class prototype (with nopaste.info or similar).

Anonymous said...

Thanks again for your help. My problem was that I did not realize that the identity could be stored in the Zend_Auth_Result object as an array. I made that change in my Auth Adapter class and it worked.