Stop ACTA

RememberMe component for CakePHP

Posted in CakePHP on 29.04.2008.

I've decided to make my "remember me" feature a bit better by creating a component.

Seeing that my article about the remember me cookie is the most popular on my site, I've decided to pack it all in a nice component.

Basically, take the code from this page (or get the whole file here) and put it here:

/app/controllers/components/remember_me.php

The code (badly formatted in an attempt to make it less scrollable):

class RememberMeComponent extends Object
{
    var $components = array('Auth', 'Cookie');
    var $controller = null;

    /**
     * Cookie retention period.
     *
     * @var string
     */
    var $period = '+2 weeks';

    /**
     * The model used to validate a user from the cookie.
     *
     * @var string
     */
    var $userModel = array(
            'name' => 'User',
            'fields' => array(
                'username' => 'username',
                'password' => 'password'
            )
        );

    /**
     * Name of the cookie and its fields.
     *
     * @var string
     */
    var $userCookie = array(
            'name' => 'User',
            'fields' => array(
                'username' => 'username',
                'token' => 'token'
            )
        );

    function startup(&$controller)
    {
        $this->controller =& $controller;
    }

    function _getModel($name = null)
    {
        $model = null;

        if (!$name)
        {
            $name = $this->userModel['name'];
        }

        $model = ClassRegistry::init($name);

        if (empty($model))
        {
            trigger_error(__('RememberMe::getModel() - Model is not set or could not be found', true), E_USER_WARNING);
            return null;
        }

        return $model;
    }

    function remember($username, $token)
    {
        $cookie = array();
        $cookie[$this->userCookie['fields']['username']] = $username;
        $cookie[$this->userCookie['fields']['token']] = $token;
        $this->Cookie->write(
            $this->userCookie['name'],
            $cookie,
            true,
            $this->period
        );
    }

    function check()
    {
        $cookie = $this->Cookie->read($this->userCookie['name']);

        if (!is_array($cookie) || $this->Auth->user())
            return;

        $model = $this->_getModel();

        $user = $model->find(
            array(
                $this->userModel['fields']['username'] =>
                    $cookie[$this->userCookie['fields']['username']],
                $this->userModel['fields']['password'] =>
                    $cookie[$this->userCookie['fields']['token']]
                ),
            array(),
            null,
            -1
            );

        if ($user)
        {
            $model->data = $user;
            $model->id = $user[$this->userModel['name']][$model->primaryKey];

            if ($this->Auth->login($model))
            {
                $this->Cookie->write(
                    $this->userCookie['name'],
                    $cookie,
                    true,
                    $this->period
                );
            }
            else
            {
                $this->delete();
            }
        }
    }

    function delete()
    {
        $this->Cookie->del($this->userCookie['name']);
    }
}

?>

The code can also be downloaded from here.

This code is slightly different from my previous version, as it does one more extra thing. When our user is logged in from a cookie, the cookie is written again and its retention period extended. By default, this cookie will expire after two weeks, but if our user visits the site often, this cookie will last forever.

Usage

To remember my user upon login, I now have this:

class UsersController extends AppController
{
    var $name = 'Users';
    var $uses = array('User');

    function login()
    {
        if (!$this->Auth->user())
        {
            if (!empty($this->data))
            {
                if (empty($this->data['User']['remember_me']))
                {
                    $this->RememberMe->delete();
                }
                else
                {
                    $this->RememberMe->remember(
                            $this->data['User']['username'],
                            $this->data['User']['password']
                        );
                }

                unset($this->data['User']['remember_me']);
            }
        }

        $this->redirect($this->Auth->redirect());
    }

    function logout()
    {
        $this->RememberMe->delete();
        $this->redirect($this->Auth->logout());
    }
}

And in my AppController, I have the following:

class AppController extends Controller
{
    var $components = array('RememberMe');

    function beforeFilter()
    {
        // component settings are customizable here,
        // much like the Auth component.. For example:
        // $this->RememberMe->period = '+2 months';

        // snip...
        $this->RememberMe->check();
        // snip..
    }
}

That's it! If you find it useful of have an improvement to suggest, please leave a note!

Happy baking!

Article comments — View · Add


Page 1 of 2
1 · 2

Baz L :: 16.05.2008 08:35:47
Why are you doing a manual find on the user? The Auth::login() takes care of that. You can feed $cookie straight to it. Might need to set "token" back to "password" though.
lecterror :: 16.05.2008 08:42:41
That's actually a very good point.

I think something didn't work when I tried that at first, but that was my first contact with Cake, I might have done something terribly wrong... :-)

I'll try it and update the article if I succeed this time. [doh]

Thanks Baz ;-)
rtconner :: 27.07.2008 22:40:27
is that really a good idea just store the users password in a cookie like that?
lecterror :: 02.09.2008 07:35:47
Hi rtconner,

I don't worry too much about that. If someone has access to your cookies, I think they are the last thing you need to worry about.. :-)
Zarate :: 28.11.2008 03:52:28
Hi!

This is not quite working for me atm. I've tracked down the problem and it's that by the time UsersController.login method is called, the data.User.Password is gone.

Not sure exactly where yet, but the Auth component is called BEFORE, does its magic, unsets the password and by the time you want to use the password value for the cookie, it's just blank.

I'm on Cake 1.2.0.7296 RC2, is this supposed to work there?

Thanks!

Juan