Implementing "Remember Me" on a Website
Even though most modern browsers can remember your usernames and passwords for you, it is nice for a web application to allow an individual to return to a web site and be automatically logged in without having to submit credentials every time they do so. Implementing "Remember Me" with the Istarel Workshop Application Framework requires several code blocks, and this article assumes that basic security has already been implemented in the application.
Update the Login Form
In the Login application module, you need to add a checkbox element.
Partial Listing: Login.php
class Login extends ApplicationModule { ... function formElements() { return array( 'header' => new IWHeaderElement('Login Information'), 'email' => new IWFormElement('Email:', new IWTextElement(30, 100)), 'password' => new IWFormElement('Password:', new IWPasswordElement(10, 12)), 'remember_me' => new IWFormElement(null, new IWCheckboxElement, 'Remember Me'), ); } ... }
Update the Login Validation
As a reminder, the ApplicationSecurityDelegate is invoked for handling login, called via its validateLogin() method. The approach I use is for the applicable user model object factory class to handle actual authentication (using $_POST as the input data). If the authentication succeeds, a cookie is established.
Partial Listing: rsrc/ApplicationSecurityDelegate.php
class ApplicationSecurityDelegate { const COOKIE_DURATION = 31536000; // one year function validateLogin($form) { // If a valid user results from authentication, login is valid if ($this->user = AppUserFactory::authenticatedUser($_POST)) { // If "remember me" is checked, set up a cookie if (IWRequest::postValue('remember_me', null)) $this->establishCookie(); return null; } // Failed validation should result in a field-keyed array of errors return array('password' => 'Invalid email address or password'); } function establishCookie() { if (! $this->user) return; // Establish the cookie setcookie( AppUser::IDENTIFIER, md5($this->user->app_user_id), time() + ApplicationSecurityDelegate::COOKIE_DURATION, APPL_ROOT_DIR ); } ...
Update User Instantiation
The ApplicationSecurityDelegate is a singleton, and its instantiation must be modified to accommodate the possilbility of a cookie-based user (much like the original change to look in the session variable).
Partial Listing: rsrc/ApplicationSecurityDelegate.php
class ApplicationSecurityDelegate protected function __construct() { } protected $user; static function sharedApplicationSecurityDelegate() { static $instance; if (! $instance) $instance = new ApplicationSecurityDelegate; $instance->initFromSession(); if (! $instance->user()) $instance->initFromCookie(); return $instance; } function initFromSession() { if (isset($_SESSION['USER'])) $this->user = $_SESSION['USER']; } function initFromCookie() { // If there is no cookie, do nothing if (! isset($_COOKIE[AppUser::IDENTIFIER])) return; // Use the cookie data to establish the current user $this->user = AppUserFactory::retrieveAppUser($_COOKIE[AppUser::IDENTIFIER], 'md5(app_user_id::text)'); // If no user could be established, do nothing if (! $this->user) return; // Establish the current session and update the cookie $this->establishSession(); $this->establishCookie(); }
Implement the Authentication method
The authenticatedUser() static method on AppUserFactory must consider whether the "Remember Me" state is active. If the checkbox has been checked, then a cookie is set if authentication succeeds. AppUser is included to show the identifier used in the cookie.
Partial Listing: rsrc/model/application/AppUser.php and rsrc/model/application/AppUserFactory.php
class AppUser extends DefaultAppUser { // used by the ApplicationSecurityDelegate const IDENTIFIER = 'iwid'; ... } class AppUserFactory extends DefaultAppUserFactory { ... static function authenticatedUser($data = null) { // If no credentials provided, authentication fails if (! $data) return null; // If no password provided, authentication fails if (! isset($data['password']) or ! $data['password']) return false; // Retrieve an AppUser object using the credentials $login = AppUserFactory::retrieveAppUser($credentials['email'], 'lower(email)'); // If no AppUser object could be created, authentication fails if (! $login) return false; // If the password does not match, authentication fails if ($login->password != md5($data['password'])) return false; // Return the authenticated user return $login; } ... }
Word of Warning
Obviously, you want to use this technology with discretion, since it performs no authentication after the individuals logs in for the first time. That's probably fine for a user on their home or work computer, but someone clicking the "Remember Me" checkbox on a public computer exposes the application to anyone that happens to wander through the browser history.