You must be logged in to view profiles.

'Remember me' - a component to add upon basic auth

By Irina Dumitrascu (irina)

A simpler component on the same lines: http://dsi.vozibrale.com/articles/view/rememberme-component-the-final-word



This component works on top of the basic auth component and provides an 'remember me' option.

The fact that the user is logged in is stored in cookies - no need for additional database tables.
It is stored together with secret verification codes so it cannot be faked by changing the cookies => it is secure.
In order to use it:
- AppController: include Cookie, Auth, AuthExtension (in this order)
- AppController: add to the constructor:
Download code $this->Auth->autoRedirect = false;

- in your Users controller change the login and logout functions to:
Download code
    function login() {
        $this->AuthExtension->checkRememberMe();
    }
    
    function logout() {
        $this->AuthExtension->logout();
        $this->Auth->logout();
        $this->redirect('/');    
    }

- add a 'remember me' checkbox to the login form:
Download code echo $form->input('remember_me', array('label' => 'remember me on this site', 'type' => 'checkbox')); 
Here is the component's code:

Component Class:

Download code <?php 
/**
 * Extends the functionality of AuthComponent to include 'remember me' functionality - the user
 * is remembered for 2 weeks even if the normal Cake session expires.
 * Based on http://www.webdevelopment2.com/cakephp-auth-component-tutorial-3/ - but evolved 
 * considerably :D
 * - the password is not stored in the cookie
 * - instead, a hash of (password + secret string) is stored (the hash is generated using the 
 * application's Salt) - so a password change renders the cookie invalid
 * - together with the username
 * - and the current time (so the cookie is not valid after two weeks even it is stil present in the
 * browser through some hack)
 * - and a hash of all these values (the hash is generated using the Security salt) so any change of
 * one variable can be detected
 * 
 * Assumptions:
 * - that your users' model is called User
 * - the field that stores the username is called username
 * - the field that stores the password is called password
 * 
 * 1) in AppController
 * - include Cookie, Auth, AuthExtension (in this order)
 * - in the constructor set 
 *         $this->Auth->autoRedirect = false;
 * 
 * 2) in your users controller
 *     function login() {
        $this->AuthExtension->checkRememberMe();
    }
    
    function logout() {
        $this->AuthExtension->logout();
        $this->Auth->logout();
        $this->redirect('/');    
    }
 * 
 * 3) in the login form add a field
    echo $form->input('remember_me', array('label' => 'remember me on this site', 'type' => 'checkbox'));
 * 
 */
class AuthExtensionComponent extends Object {
    const 
cookie_name 'preferences'// deceiving name :D
    
const cookie_expire_string '+2 weeks';
    const 
cookie_expire_seconds 1209600//2 * 7 * 24 * 60 * 60;
    
    
var $controller null;
    
    function 
initialize(&$controller) {
        
$this->controller $controller;
        if (
$controller->Auth->user()) {
            
// already authenticated
            
return;
        }
        
$cookie $controller->Cookie->read(AuthExtensionComponent::cookie_name);
        if (!
$cookie) {
            return;
        }
        
        
$all_fields = isset($cookie['username']) && isset($cookie['hash1'])
             && isset(
$cookie['time']) && isset($cookie['hash']); 

        
// all fields present?
        
if (!$all_fields) {
            
$this->logout();
            return;
        }
        
// global hash correct?
        
if (Security::hash($cookie['username'] . $cookie['hash1'] . $cookie['time']) 
            !== 
$cookie['hash']) {
            
$this->logout();
            return;
        }
        
         if ((
time() - $cookie['time']) > AuthExtensionComponent::cookie_expire_seconds) {
            
$this->logout();
             return;
         }
        
        
// find the user
        
App::import('Model''User');
         
$User = new User();
        
$u $User->findByUsername($cookie['username']);
        if (!
$u) {
            
$this->logout();
            return;
        }
        
        if (
Security::hash($u['User']['password'] . 'another random string'nulltrue
            === 
$cookie['hash1']) {
            
// user confirmed
            
$login_array = array('User' => array(
                
'username' => $u['User']['username'],
                
'password' => $u['User']['password']));
            
$u null;
            
            if (
$controller->Auth->login($login_array)) {
                
//  Clear auth message, just in case we use it.
                
$controller->Session->del('Message.auth');
                
$controller->redirect($controller->Auth->redirect());
            } else { 
// Delete invalid Cookie
                
$this->logout();
            }
        } else {
            
$u null;        
        }
    }
    
    function 
checkRememberMe() {
        
// Auth->autoRedirect must be set to false (i.e. in a beforeFilter) for this to work
        
$auth_user $this->controller->Auth->user();
        if (
$auth_user) {
            if (!empty(
$this->controller->data) && $this->controller->data['User']['remember_me']) {
                
$u $this->controller->User->findById($auth_user['User']['id']);
                
                
$cookie = array();
                
$cookie['username'] = $u['User']['username'];
                
$cookie['hash1'] = Security::hash(
                    
$u['User']['password'] . 'another random string'nulltrue);
                
$cookie['time'] = time();
                
$cookie['hash'] = Security::hash(
                    
$cookie['username'] . $cookie['hash1'] . $cookie['time']); 
                
$this->controller->Cookie->write('preferences'$cookietrue
                    
AuthExtensionComponent::cookie_expire_string);
                unset(
$this->controller->data['User']['remember_me']);
                
$u null;
            } else {
                
// if there is a cookie, it's not good (the user would not have used the login form)
                
$this->logout();
            }
            
$this->controller->redirect($this->controller->Auth->redirect());
            return;
        }
    }
    
    function 
logout() {
        
$this->controller->Cookie->del(AuthExtensionComponent::cookie_name);
    }
}
?>

 

Comments 851

CakePHP Team Comments Author Comments
 

Comment

1 Customization

Instead of assuming that model is called User and the fields will be called username and password, it would be better to use those as default names only if the developer doesn't specifically rename them in the initialization. Or at least, name these as constants in top of file, to allow for easy change.

Other than that, thanks for sharing :)
Posted Nov 17, 2008 by Alexander Morland
 

Comment

2 best place to add necessary components

I am relatively new to cake and PHP, so this may be obvious to someone else. In implementing this component, I am trying to be as memory efficient as possible. As such can I not add the Cookie and AuthExtension component references to just the users_controller? I already have the Auth component added to app_controller.
Paul Earley
Posted Nov 21, 2008 by Paul Earley
 

Comment

3 Customization

I'm not so deep into CAKEPHP, but I think this is a nice contribution. However, with the way the Auth component currently handles user sessions, it's most likely that someone using Auth component would have customized the login and logout functions for their specific needs. In that case, how would you use this component?
Posted Nov 28, 2008 by Segun
 

Comment

4 @adding components

Hi,

thanks for your interest in this component!

in added the Cookie and AuthExtension components to the Application controller because their role is to check, for every request, whether the user is logged in (via the 'remember me' cookie) or not.

You could optimize this in your application if you have only specific pages that require the user to be authenticated (move the checks and the component inclusions from AppController to the controller(s) that need it). However, my assumption is that in most applications you have almost all the pages depending on the authentication status.
Posted Nov 28, 2008 by Irina Dumitrascu
 

Comment

5 @customizations

Hi,

when having some customized code in login/logout, one should merge the code for checking 'remember me authentication' with his own code:

- in login:
$this->AuthExtension->checkRememberMe();  this checks whether the user submitted the login form, with 'remember me' checked; if yes, it sets the cookie

- in logout:
the keyline is
$this->AuthExtension->logout(); that removes the 'remember me' cookie

the other two lines are just normal logout method call and a redirect to the site root (this is what you normally have in an application in this function):
        $this->Auth->logout();
        $this->redirect('/');   

Posted Nov 28, 2008 by Irina Dumitrascu
 

Comment

6 a more simple solution

I believe my solution does the same thing with only a fraction of your code. Also, it doesn't assume your user model is called User (well, not for a while now!:)).

http://dsi.vozibrale.com/articles/view/rememberme-component-the-final-word
Another thing, I don't understand why are you complicating things with additional hash in the cookie? I a bad guy has your cookie, it doesn't matter how the hash is created, so it doesn't make any sense to complicate the process (especially since hashing is a bit expensive).
Posted Dec 5, 2008 by Hannibal Lecter
 

Comment

7 Re:

Hi Hannibal,

thanks! Your component looks cool - basically being the same mechanism without the hashing part. As for the hashing, indeed there is not enough protection added by it.

Therefore I will just link to you component - leave a word if you improve it even further or if you post it on bakery yourself.

Irina
Posted Dec 11, 2008 by Irina Dumitrascu
 

Comment

8 Good Work

Thanks for your contribution
Posted Mar 6, 2010 by Guillermo Mansilla