So, as I as spent half the day today working on getting my “Remember Me” function to work, I figured it would be a good thing to share with the rest of the world, so that they didn’t have to ever EVER put up with the kind of stuff I went through today.
So, first of all, we all know what a remember me function is, right?

Yup, that's it (Wordpress)

And there it is again (google)
The “Remember me” function is a little checkbox that the user checks when they log in so that they don’t have to bother logging in again as long as they keep visiting the site every XX days where XX is the number of days that the site will remember them for.
Sounds easy! Let’s do it in cake!
Great idea! Cake should make it super easy! And if you search for cake and remember me functions, a lot (or rather a lot of variations based around 2 or 3 guys’ code) of sites will pop up. I personally recommend the RememberMe component from the Neutrino CMS, as it’s small, lightweight and doesn’t take much configuring. (And don’t believe the page that comes up when you enter “CakePHP rememberme” into google. That’s what got me off on the wrong foot)
And if you plan on something really simple with your site (i.e. logged in or not logged in) then it’s really easy to set up.
- Get the component that you’re going to use (I used RememberMe)
- Plop it in the components.
- Set your references to it in your users/login function, and
- (The most important part) attach the component’s cookie checking function to the app_controller’s beforeRender function.
Now, why’s the last step the most important? Because the “remember me” cookie needs to be checked for every time a page is visited, otherwise it can’t do it’s job. The first version of the remember me code I had used when I first started CakePHP had the cookie management in the users/login function. Like this:
function login() {
//-- code inside this function will execute only when autoRedirect was set to false (i.e. in a beforeFilter).
if ($this->Auth->user()) {
if (!empty($this->data) && $this->data['User']['remember_me']) {
$cookie = array();
$cookie['username'] = $this->data['User']['username'];
$cookie['password'] = $this->data['User']['password'];
$this->Cookie->write('Auth.User', $cookie, true, '+2 weeks');
unset($this->data['User']['remember_me']);
}
$this->redirect($this->Auth->redirect());
}
if (empty($this->data)) {
$cookie = $this->Cookie->read('Auth.User');
if (!is_null($cookie)) {
if ($this->Auth->login($cookie)) {
// Clear auth message, just in case we use it.
$this->Session->del('Message.auth');
$this->redirect($this->Auth->redirect());
} else { // Delete invalid Cookie
$this->Cookie->del('Auth.User');
}
}
}
}
Makes perfect sense, right?
NO
The problem is that users/login only gets called when there’s a login to be processed (which is not explained very well in the documentation). This becomes a problem when you are using the Auth->allow() array to let people access parts of the site, and not others. (Even more of a problem when the same page has different data for logged in and non-logged in users)
What happens is that since the action is allowed, Cake decides that there’s no reason to check for a login, so even if the login cookie exists, it doesn’t get checked because the Users/Login function never gets called.
So, in most cases, this is not a problem — just put the RememberMe->check() function into the AppController’s beforeRender — but what if you want do something more after a user logs in? Something like increasing their login count, setting login time, getting info about them, etc etc.
Custom Functions and Remember Me
Most RememberMe components and solutions have a provision for callbacks after a successful cookie login (much like isAuthorized) however (also like isAuthorized) the function has to be in either:
- The AppController
- The Current Controller
Because the component only has access to the controller that’s currently calling it, you’re confined to either the code in the AppController or the currently viewed controller. And if your current controller is Articles (because your user is looking at an article) then we can’t directly access the users controller to run our little _after_login function. (With Models you can get to from almost anywhere in your code through skillful use of relations, but Controllers are a bit trickier)
So, what do we do? We take a little hint from the cake library, and use the wonderful App:import function to forcibly bring the Controller to us. Now this is an expensive call, because it’s essentially ringing up an entire new controller with all its associated models, et al. So we need to be sure to do this only when the user is logging in through a cookie. That way, it’ll only happen once for the user’s session, and we can quickly get rid of its memory-eating load on the next page view.
So, let’s look at some code:
The Original RememberMe component
<?php
class RememberMeComponent extends Object
{
var $components = array('Auth', 'Cookie');
var $controller = null;
/**
* Cookie retention period.
*
* @var string
*/
var $period = '+2 weeks';
var $cookieName = 'User';
function startup(&$controller)
{
$this->controller =& $controller;
}
function remember($username, $password)
{
$cookie = array();
$cookie[$this->Auth->fields['username']] = $username;
$cookie[$this->Auth->fields['password']] = $password;
$this->Cookie->write($this->cookieName, $cookie, true, $this->period);
}
function check()
{
$cookie = $this->Cookie->read($this->cookieName);
if (!is_array($cookie) || $this->Auth->user())
return;
if ($this->Auth->login($cookie))
{
$this->Cookie->write($this->cookieName, $cookie, true, $this->period);
}
else
{
$this->delete();
}
}
function delete()
{
$this->Cookie->del($this->cookieName);
}
}
?>
So, this check function has got to go
We need to be able to load the UsersController so that we can get to our _post_login() function to do all our login magic like checking how the user’s subscription is going, how long many times they’ve logged in, etc.
function check()
{
// If you want to change the cookie name, change it here
$this->Cookie->name = 'rememberme';
$cookie = $this->Cookie->read($this->cookieName);
if (!is_array($cookie) || $this->Auth->user())
return;
if ($this->Auth->login($cookie))
{
$this->Cookie->write($this->cookieName, $cookie, true, $this->period);
if (!App::import('Controller', 'Users')) {
return false;
}
$className = 'UsersController';
$Ctrl =& new $className();
$Ctrl->constructClasses();
$Ctrl->_post_login($this->Auth->user('id'));
}
else
{
$this->delete();
}
}
So, here’s my code as it stands now. What I’ve done is if there is a cookie, and you can successfully login with said cookie (Through $this->Auth->login($cookie) ) then we’re going to spin up the Controller called Users through the App:import function. If it can’t load, then we return false, but if it DOES load, then we create a new Controller and construct it so that we can then call our post_login script with the Auth->user Info. Everything else is set up just how the instructions on the RememberMe Component Page say.
Conclusion
Getting a RememberMe function working in CakePHP really isn’t that difficult, but the problem (like a lot of CakePHP) is that the signal-to-noise ratio of those who know what they’re doing and those that don’t is incredibly high. I don’t profess to have all the answers, but hopefully just some of the problems that I’ve had will help others who are struggling with the same issues.
Till next time!

Hi there!
Sorry for my *very* late reaction, I’ve been slightly busy lately…
Now about your modifications; wouldn’t it be better to move _post_login() function to your User model? I mean, if everything you do in it is actually handling data and doesn’t really have anything to do with application logic, a model is a much more logical place, don’t you agree?
Also, the line you added in the check() function [$this->Cookie->name = 'rememberme';] defeats the purpose of component’s “cookieName” var, which should be used for customization. I don’t think it should work either, I don’t see it being used..?
What’s your opinion?
Dr. Lecter
No problem about the being busy! I definitely know the feeling!
I would like to put the _post_login() function in the model but I didn’t for two reasons:
1)
I had a lot of cookie, auth->user and redirecting logic that I needed to take care of in addition to the regular data manipulation, so I couldn’t take care of all of it in-model without passing a lot of references to controller-only functions. If I was dealing only with data logic, then I would have stuck it all in the model, but the cookies and Auth access is what stopped me.
2)
I wanted to show people how to forcibly add a controller.
I’m trying more and more to get the logic out of my controllers and into my models, but I find that a lack of access to cookies, session and Auth really hurt that.
I hate having to pass a ton of variables whenever I want to call a simple function. I find that it ends up taking more code to prepare and pass the data to the model than it would to use a built-in model function such as find() or save() and just create the query variables in-controller.
And every time I do it, I feel like a bad baker.
Also, regarding the cookie, I was noticing that the supplied cookie name wasn’t working, nor were my attempts to change the cookie name in the config file. The line I added was merely a cheap hack to solve a problem that I really couldn’t figure out. :/