AutoLogin Component - An Auth remember me feature

This article is also available in the following languages:
By milesj
A user can save their login information by ticking off a checkbox in the login form and AutoLogin will store their information in a cookie to automatically log them in (using the Auth Component) on their next visit.
Below are a list of features present within the AutoLogin. Also thanks to all the other authors with similar scripts.

* Requires no installation except for adding the checkbox into your user login forms
* Automatically saves the cookie and info when a user logs in
* Automatically kills the cookie and session when a user logs out
* Inserts a hash within the cookie so that it cannot be hijacked
* Encrypts the cookie so the information cannot be harvested
* Configuration options for cookie name and length
* Functionality for additional user updating or error logging

Code

You can view the original documentation and newer versions here!

Component Class:

<?php 
/** 
 * auto_login.php
 *
 * A CakePHP Component that will automatically login the Auth session for a duration if the user requested to (saves data to cookies). 
 *
 * Copyright 2006-2009, Miles Johnson - www.milesj.me
 * Licensed under The MIT License - Modification, Redistribution allowed but must retain the above copyright notice
 * @link         http://www.opensource.org/licenses/mit-license.php
 *
 * @package        AutoLogin Component
 * @created        May 27th 2009
 * @version     1.4
 * @link        www.milesj.me/resources/script/auto-login
 * @changelog    www.milesj.me/files/logs/auto-login 
 */

class AutoLoginComponent extends Object {

    
/**
     * Current version: www.milesj.me/files/logs/auto-login
     * @var string
     */
    
var $version '1.4';

    
/**
     * Components
     * @var array 
     */
    
var $components = array('Cookie');
    
    
/**
     * Cookie name 
     * @var string
     */
    
var $cookieName 'autoLogin';
    
    
/**
     * Cookie length (strtotime())
     * @var string
     */
    
var $expires '+2 weeks';   
    
    
/**
     * Settings
     * @var array
     */
    
var $settings = array(
        
'controller' => '',
        
'loginAction' => '',
        
'logoutAction' => ''
    
);
    
    
/**
     * Automatically login existent Auth session; called after controllers beforeFilter() so that Auth is initialized
     * @param object $Controller 
     * @return void 
     */
    
function startup(&$Controller) { 
        
$cookie $this->Cookie->read($this->cookieName);   
        
        if (!
is_array($cookie) || $Controller->Auth->user()) {
            return;
        }
        
        if (
$cookie['hash'] != $Controller->Auth->password($cookie[$Controller->Auth->fields['username']] . $cookie['time'])) {
            
$this->delete();
            return;
        }

        if (
$Controller->Auth->login($cookie)) {
            if (
in_array('_autoLogin'get_class_methods($Controller))) {
                
call_user_func_array(array(&$Controller'_autoLogin'), array($Controller->Auth->user()));
            }
        } else {
            if (
in_array('_autoLoginError'get_class_methods($Controller))) {
                
call_user_func_array(array(&$Controller'_autoLoginError'), array($cookie));
            }
        }
        
        return 
true;
    }
    
    
/**
     * Automatically process logic when hitting login/logout actions
     * @param object $Controller  
     * @param array $url
     * @param boolean $status
     * @param boolean $exit
     * @return void
     */
    
function beforeRedirect(&$Controller$url$status null$exit true) { 
        
$controller     $this->settings['controller'];
        
$loginAction     $this->settings['loginAction'];
        
$logoutAction     $this->settings['logoutAction'];
        
        if (
is_array($Controller->Auth->loginAction)) {
            if (!empty(
$Controller->Auth->loginAction['controller'])) {
                
$controller Inflector::camelize($Controller->Auth->loginAction['controller']);
            }
            
            if (!empty(
$Controller->Auth->loginAction['action'])) {
                
$loginAction $Controller->Auth->loginAction['action'];
            }
        }
        
        if (!empty(
$Controller->Auth->userModel) && empty($controller)) {
            
$controller Inflector::pluralize($Controller->Auth->userModel);
        }
        
        if (empty(
$loginAction)) {
            
$loginAction 'login';
        }
        
        if (empty(
$logoutAction)) {
            
$logoutAction 'logout';
        }
        
        
// Is called after user login/logout validates, but befire auth redirects
        
if ($Controller->name == $controller) {
            
$data $Controller->data;
            
            switch (
$Controller->action) {
                case 
$loginAction:
                    
$username $data[$Controller->Auth->userModel][$Controller->Auth->fields['username']];
                    
$password $data[$Controller->Auth->userModel][$Controller->Auth->fields['password']];
                    
                    if (!empty(
$username) && !empty($password) && $data[$Controller->Auth->userModel]['auto_login'] == 1) {
                        
$this->save($username$password$Controller);
                    } else if (
$data[$Controller->Auth->userModel]['auto_login'] == 0) {
                        
$this->delete();
                    }
                break;
                
                case 
$logoutAction:
                    
$this->delete();
                break;
            }
        }
    }

    
/**
     * Remember the user information
     * @param string $username
     * @param string $password
     * @param object $Controller
     * @return void
     */
    
function save($username$password$Controller) {
        
$time time();
        
$cookie = array();
        
$cookie[$Controller->Auth->fields['username']] = $username;
        
$cookie[$Controller->Auth->fields['password']] = $password// already hashed from auth
        
$cookie['hash'] = $Controller->Auth->password($username $time);
        
$cookie['time'] = $time;
        
        
$this->Cookie->write($this->cookieName$cookietrue$this->expires);
    }

    
/**
     * Delete the cookie
     * @return void
     */
    
function delete() {
        
$this->Cookie->del($this->cookieName);
    }
    
}
?>

Installation

If you haven't already, grab the script above and place the code in a file called auto_login.php within your app/controllers/components/ folder. Once you have done that, simply add AutoLogin into your controllers $components property. AutoLogin must be placed before Auth in the $components array or it will not work properly.

var $components = array('AutoLogin', 'Auth');
The AutoLogin component will automatically save the user info to a cookie when they login at users/login/. It also works when logging out at users/logout/, by removing the cookie.

The final step is to create a checkbox in your login form named auto_login. The model used in the form should also match the User model you are using in your Auth.

<?php echo $form->input('auto_login', array('type' => 'checkbox''label' => 'Log me in automatically?')); ?>

Configuration

If you would like to change the name of the cookie, or the duration until the cookie expires (defaults to 2 weeks), you can change it in your AppController's beforeFilter().

<?php
function beforeFilter() {
    
$this->AutoLogin->cookieName 'rememberMe';
    
$this->AutoLogin->expires '+1 month';
}

If for some reason the controller name and the login/logout action names are not default (whats based in Auth), you can change them in the $settings array (in beforeFilter() of course).

<?php
$this
->AutoLogin->settings = array(
    
'controller' => 'Members',
    
'loginAction' => 'signin',
    
'logoutAction' => 'signout'
);

Adding your own logic or logging

If you need to do additional logging and updating that is not initially in Auths user login (for example updating a users last login time), you can place this extra code in a method called _autoLogin() within your AppController. Also if Auth login fails, you can do some error logging and reporting by creating a method called _autoLoginError(). Both of these will be called automatically and only if the method exists.

<?php
class AppController extends Controller {

    
/**
     * Run whenever auto login is successful
     * @param array $user - The Auth user session
     * @access private
     */
    
function _autoLogin($user) {
    }
    
    
/**
     * Run whenever auto login fails
     * @param array $cookie - The login cookie data
     * @access private
     */
    
function _autoLoginError($cookie) {
    }
    
}

Comments

  • Posted 01/09/11 06:58:58 AM
    In version 1.6, line 142: $autoLogin = (isset($data[$Controller->Auth->userModel]['auto_login'])) ? $data[$Controller->Auth->userModel]['auto_login'] : 0; i think isn't correct, and this should be:
    $autoLogin = (isset($data[$Controller->Auth->userModel]['auto_login'])) ? $data[$Controller->Auth->userModel]['auto_login'] : 1;

    so if we have data send from login form $autoLogin var will be = with checkbox value and else $autoLogin is 1
  • Posted 07/13/10 03:11:39 AM
    Hi there,

    thanks for the component. I had a problem with $this->Cookie->del(), which did not work for me (CakePHP 1.3.2). I changed it to $this->Cookie->destroy(), what worked out.

    Best wishes, Mathias Wellner
  • Posted 02/17/10 10:57:28 PM
    My installation wasn't working until I added Cookie to the component list. How does this work without it?
  • Posted 02/16/10 09:07:11 AM
    Second, I found is a very very annoying warning :
    Warning (512): You cannot use an empty key for Security::cipher()

    I had the same issue and it was gone when I added the Cookie component to my controller
    • Posted 01/03/11 11:36:34 PM
      [quote] Second, I found is a very very annoying warning :
      Warning (512): You cannot use an empty key for Security::cipher()

      I had the same issue and it was gone when I added the Cookie component to my controller
      [end quote]
      You can fix this by adding a 3rd param of false when you're setting/writing your cookie. So...
      $this->Cookie->write('key', 'value', false);
      This tells the Cookie component to not use any encryption
  • Posted 12/09/09 04:14:24 PM
    Hi there,

    I tried the component today.

    First, thanks for the work done, really useful.

    Second, I found is a very very annoying warning :

    Warning (512): You cannot use an empty key for Security::cipher()

    coming from the writing of the cookie :

    $this->Cookie->write($this->cookieName, $cookie, true, $this->expires);

    Anybody ever experienced this problem?

    Does anybody know how to solve this issue?
  • Posted 12/01/09 04:49:34 PM
    I'm new so bare with me.
    I'm pretty sure I did everything correctly. I put the AutoLogin component before the Auth in the AppController. I have a user model that has a username and password fields. But when I installed the autologin script I got the following three lines:

    Notice (8): Undefined index: auto_login [APP\controllers\components\auto_login.php, line 128]
    Notice (8): Undefined index: auto_login [APP\controllers\components\auto_login.php, line 130]
    Warning (2): Cannot modify header information - headers already sent by (output started at C:\Users\david\Documents\sites\zipnit\cake\basics.php:109) [CORE\cake\libs\controller\controller.php, line 644
  • Posted 09/22/09 11:08:32 AM
    You can just add this logic to Users/login (or any other controller/action you specified).

    Controller Class:

    <?php 
    class UsersController extends AppController {

        function 
    beforeFilter() {
            
    // set this to have a custom login function
            
    $this->Auth->autoRedirect false;
        }

        function 
    login() {
            if (empty(
    $this->data)) {
                
    // read Auth Cookie and login
            
    }
            if (
    $this->Auth->user('id')) {
                if (
    $this->data['User']['remember_me']) {
                    
    // set the cookie
                
    }
            }
        }

        function 
    logout() {
            
    // delete cookie
        
    }
    }
    ?>
  • Posted 09/20/09 03:52:22 AM
    Warning (2): Cannot modify header information - headers already sent by (output started at /var/www/mydomain/app/controllers/components/auto_login.php:167)
    this appears when i try to login.
    my login works fine before ;-)

    line 167 is the last line of the script with
    }?> 
  • Posted 08/07/09 10:28:04 AM
    I put in the changes. Initialization. Controller namings. Commenting out destructor call. I think the commenting out the destructor call was the big win.

    Thanks! I was spending as much time logging in as working.
  • Posted 07/21/09 12:02:18 PM
    Hello,

    I've got the same problem as Daniel Bard did above. Everything works great, but then I delete my PHP Session cookie. AutoLogin logs me back in properly, but also deletes the Cake[AutoLogin] cookie. So, if I repeat the delete-session-cookie event, I no longer have a valid autologin cookie.

    Doing some investigating, it's on line 127 of auto_login.php that is causing this error. It seems my controller->data is null, which makes AutoLogin think that the cookie should be deleted, and so it does so. Note that $Controller inside the beforeRedirect method isn't null -- just the data attribute. Commenting out line 128 ($this->delete()) gets me the desired functionality, but I also think that it will cause all my users to autologin, whether or not they like it.

    Any advice?

    Thanks,
    Travis
  • Posted 07/12/09 07:57:55 PM
    This works great. But I'm experiencing a problem: I'm not immediately logged in when visiting the page, only after I click on a link. Eg: When I visit a page, say: mysite.com/invoices... I don't get logged in, but when I click on a link, (mysite.com/invoices/add) I get automatically logged in. (Refreshing the page also logs me in.) Any suggestions?

    **UPDATE: It appears its because this component runs AFTER beforeFilter(), where I set user settings and do some Auth work. How would I go about changing the component to work with an "initialize()" method instead, so as to run BEFORE beforeFilter()?
    • Posted 02/09/11 01:36:55 AM
      [quote] This works great. But I'm experiencing a problem: I'm not immediately logged in when visiting the page, only after I click on a link. Eg: When I visit a page, say: mysite.com/invoices... I don't get logged in, but when I click on a link, (mysite.com/invoices/add) I get automatically logged in. (Refreshing the page also logs me in.) Any suggestions?

      **UPDATE: It appears its because this component runs AFTER beforeFilter(), where I set user settings and do some Auth work. How would I go about changing the component to work with an "initialize()" method instead, so as to run BEFORE beforeFilter()?
      [end quote]
      I also have the same problem, did some body solve this problem?
    • Posted 07/17/09 06:08:55 PM
      This works great. But I'm experiencing a problem: I'm not immediately logged in when visiting the page, only after I click on a link. Eg: When I visit a page, say: mysite.com/invoices... I don't get logged in, but when I click on a link, (mysite.com/invoices/add) I get automatically logged in. (Refreshing the page also logs me in.) Any suggestions?

      **UPDATE: It appears its because this component runs AFTER beforeFilter(), where I set user settings and do some Auth work. How would I go about changing the component to work with an "initialize()" method instead, so as to run BEFORE beforeFilter()?

      It runs after beforeFilter() so that all your component settings are set. If you make it run during init, no settings will be applied.
  • Posted 06/07/09 09:58:32 AM
    I've been experimenting with this component and it works great. A minor problem, the cookie it sets seems to get deleted after the first auto login so it will only automatically log the user in once.

    To replicate, just delete the session cookie.
    • Posted 06/07/09 03:13:26 PM
      I've been experimenting with this component and it works great. A minor problem, the cookie it sets seems to get deleted after the first auto login so it will only automatically log the user in once.

      To replicate, just delete the session cookie.

      I have yet to receive this problem. What is your expires date, and your cookie settings in your browser?
      • Posted 09/19/09 06:33:56 AM
        I've been experimenting with this component and it works great. A minor problem, the cookie it sets seems to get deleted after the first auto login so it will only automatically log the user in once.

        To replicate, just delete the session cookie.

        I have yet to receive this problem. What is your expires date, and your cookie settings in your browser?

        I have the same issue. Reproduction:
        - I login and the cookie is nicely set with an expiration 2 week down the line.
        - I select a page that has no public access
        - I delete the session cookie
        - I refresh the page, get a security warning that I have no access
        - the AutoLogin cooke is gone, there is a new session cookie
        - I refesh the page again, the page shows with full access

        So, the cookie is set, the cookie is accepted but is deleted once accepted...

        I traced the issue to the component, function BeforeRedirect:

        Model Class:

        <?php if (!empty($username) && !empty($password) && $data[$Controller->Auth->userModel]['auto_login'] == 1) {

            
        $this->save($username$password$Controller);

        } else if (
        $data[$Controller->Auth->userModel]['auto_login'] == 0) {
            
        $this->delete();

        }
        ?>
        The value of $data[$Controller->Auth->userModel]['auto_login'] seems to be blanc (not zero), but it does enter this 'else' block.
        • Posted 10/07/09 02:12:47 PM
          I've been experimenting with this component and it works great. A minor problem, the cookie it sets seems to get deleted after the first auto login so it will only automatically log the user in once.

          To replicate, just delete the session cookie.

          I have yet to receive this problem. What is your expires date, and your cookie settings in your browser?

          I have the same issue. Reproduction:
          - I login and the cookie is nicely set with an expiration 2 week down the line.
          - I select a page that has no public access
          - I delete the session cookie
          - I refresh the page, get a security warning that I have no access
          - the AutoLogin cooke is gone, there is a new session cookie
          - I refesh the page again, the page shows with full access

          So, the cookie is set, the cookie is accepted but is deleted once accepted...

          I traced the issue to the component, function BeforeRedirect:

          Model Class:

          <?php if (!empty($username) && !empty($password) && $data[$Controller->Auth->userModel]['auto_login'] == 1) {

              
          $this->save($username$password$Controller);

          } else if (
          $data[$Controller->Auth->userModel]['auto_login'] == 0) {
              
          $this->delete();

          }
          ?>
          The value of $data[$Controller->Auth->userModel]['auto_login'] seems to be blanc (not zero), but it does enter this 'else' block.

          In AutoLogin Component v1.6:
          try to replace code in rows 144-146:

          if (!empty($username) && !empty($password) && $autoLogin == 1) {
              $this->save($username, $password, $Controller);
          } else if ($autoLogin == 0) {
              $this->delete();
          }

          by these lines:

          if (!empty($username) && !empty($password)) {
              if ($autoLogin) {
                  $this->save($username, $password, $Controller);
              } else {
                  $this->delete();
              }
          }
      • Posted 06/23/09 03:51:34 PM
        I have yet to receive this problem. What is your expires date, and your cookie settings in your browser?
        Sorry for not replying sooner, I never received an email when the comment was posted.

        I've fixed the problem, I think it was how I was redirecting in my login action which was causing problems.

        There is another problem though, if the user is accessing a protected area when the autologin occurs, the user is redirected back to the login box. Pressing F5 and the correct page is displayed.

        I went on IRC for help and ADmad said "the autologin component is added after auth in the components list... so the startup method of auth compoent (which checks is logged in else redirects) is executed before the autologin components startup (which checks for cookie and logs in).. you must be encountering this only when you try to access a protected page directly first time in a browser session, right ?"

        Everything is at defaults by the way.

        Edit: It does seem like if you switch the two components around so that AutoLogin is first, it starts to work correctly.
  • Posted 06/04/09 09:27:08 AM
    Thanks a lot for your solution! Works like a charm!
  • Posted 06/03/09 09:43:34 AM
    Hello, Miles.

    I find it a bit weird that you only get the Auth default settings if the AutoLogin settings are empty, which, by default, are not.

    So, in my application, I'd have to configure my controller and login action twice: once for the AuthComponent, and once for the AutoLoginComponent.

    I've noticed, as well, that the "name" property of a controller is camelcase, and you compare it with an underscored word, and so the condition is always going to be false.

    If you like my suggestions, here's a patch (which could probably be improven):

    --- a/app/controllers/components/auto_login.php
    +++ b/app/controllers/components/auto_login.php
    @@ -46,9 +46,9 @@ class AutoLoginComponent extends Object {
    * @var array
    */
    var $settings = array(
    - 'controller' => 'Users',
    - 'loginAction' => 'login',
    - 'logoutAction' => 'logout'
    + 'controller' => null,
    + 'loginAction' => null,
    + 'logoutAction' => null
    );

    /**
    @@ -105,7 +105,7 @@ class AutoLoginComponent extends Object {
    }

    if (!empty($Controller->Auth->userModel) && empty($controller)) {
    - $controller = Inflector::underscore(Inflector::pluralize($Controller->Auth->userModel));
    + $controller = Inflector::pluralize($Controller->Auth->userModel);
    }

    if (empty($loginAction)) {
    --

    Thanks.
    • Posted 06/03/09 03:14:40 PM
      @@ -105,7 +105,7 @@ class AutoLoginComponent extends Object {
      }

      if (!empty($Controller->Auth->userModel) && empty($controller)) {
      - $controller = Inflector::underscore(Inflector::pluralize($Controller->Auth->userModel));
      + $controller = Inflector::pluralize($Controller->Auth->userModel);
      }

      if (empty($loginAction)) {
      --

      Thanks.

      Thanks Javier, you are correct about the first part, the settings should be empty.

      As for the second part about inflecting the Controller name, I got it exactly from the Auth component. Its within __setDefaults(), line 422. But then again, I guess it wont match $this->name.

      'controller'=> Inflector::underscore(Inflector::pluralize($this->userModel))
  • Posted 06/03/09 08:32:52 AM
    I unpublished your comment as I published the article. Greate work. Thank you.
  • Posted 06/02/09 12:38:27 PM
    I updated the code to grab the controller name from the Auth component, then fallsback to Users if not set.
    Its an improvement but you still have login/logout action names hardcoded. Just as you use Auth->loginAction to check for controller name same way check for action name too and similarly for logout check Auth->logoutAction. Also this code $controller = ucfirst(strtolower($this->Auth->loginAction['controller']));  will produce incorrect controller name for multi-worded controller name, so please fix that.
  • Posted 06/02/09 06:09:58 AM
    Should it not also use $Controller->Auth-> isntead of $this->Auth?
  • Posted 06/01/09 12:16:19 PM
    Thank you for your contribution but your component is hard coded to work with only users controller; line 1 of beforeRedirect: if ($controller->name == 'Users') Please make it customizable so that i can be used with any controller

Comments are closed for articles over a year old