'Remember me' - a component to add upon basic auth
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
- in your Users controller change the login and logout functions to:
Download code
- add a 'remember me' checkbox to the login form:
Download code
Here is the component's code:
- 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', null, true)
=== $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', null, true);
$cookie['time'] = time();
$cookie['hash'] = Security::hash(
$cookie['username'] . $cookie['hash1'] . $cookie['time']);
$this->controller->Cookie->write('preferences', $cookie, true,
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
Comment
1 Customization
Other than that, thanks for sharing :)
Comment
2 best place to add necessary components
Paul Earley
Comment
3 Customization
Comment
4 @adding components
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.
Comment
5 @customizations
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' cookiethe 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('/');
Comment
6 a more simple solution
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).
Comment
7 Re:
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
Comment
8 Good Work