AutoLogin Component - An Auth remember me feature

By Miles Johnson (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:

Download code <?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.

Download code 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.

Download code <?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().

Download code <?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).

Download code <?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.

Download code <?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 1055

CakePHP Team Comments Author Comments
 

Comment

1 Auth defaults

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 Jun 3, 2009 by Javier
 

Comment

2 Reply

@@ -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 Jun 3, 2009 by Miles Johnson
 

Comment

3 Works beautifully

Thanks a lot for your solution! Works like a charm!
Posted Jun 4, 2009 by Lucas Prim
 

Bug

4 AutoLogin Cookie

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 Jun 7, 2009 by Daniel Harris-Baird
 

Comment

5 Whats your cookie settings?

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 Jun 7, 2009 by Miles Johnson
 

Comment

6 Redirection problem

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 Jun 23, 2009 by Daniel Harris-Baird
 

Question

7 Visiting page doesnt log me in, not untill I click on a link.

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 Jul 12, 2009 by Jannus Meyburg
 

Comment

8 Hmm

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 Jul 17, 2009 by Miles Johnson
 

Question

9 Same issue as #4-6

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 Jul 21, 2009 by Travis Leleu
 

Comment

10 Didn't work off the bat

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 Aug 7, 2009 by marc condon
 

Comment

11 AutoLogin Cookie

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 Sep 19, 2009 by Michiel Boertje
 

Bug

12 headers already sent

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 Sep 20, 2009 by Alex Marquardt
 

Question

13 Do you really need a component for this?

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 Sep 22, 2009 by Thomas Ploch
 

Comment

14 Autologin cookie deleted after successful autologin

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 Oct 7, 2009 by Sergey Rodovnichenko
 

Comment

15 Didn't work for me

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 Dec 1, 2009 by David Marks
 

Comment

16 Annoying warning

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 Dec 9, 2009 by Thomas Auss