28

Identifying users

Create site14 by copying site13.

  1. /cms
    1. ...
    2. site13
    3. site14

In this chapter, we are going to add a form to authenticate users.

To test the result online, enter http://www.frasq.org/cms/site14 in the address bar of your navigator. In the banner of the home page, click on the icon which shows a couple of users. Fill in the form with foobar for the name and f00bar for the password. Enter the verification code. Press Enter. Click on the red icon with a cross in the banner of the home page or the contact page to disconnect.

Start by creating the actions user and nobody by adding the files user.php and nobody.php in the folder actions with the following contents:

  1. /cms/site14
    1. actions
      1. user.php
      2. nobody.php
  1. function user($lang) {
  2.     $login = build('login', $lang);
  3.     if (true === $login) {
  4.         $next_page = url('home', $lang);
  5.  
  6.         header("Location: $next_page");
  7.         return false;
  8.     }
  9.  
  10.     $banner = build('banner', $lang);
  11.     $content = view('user', $lang, compact('login'));
  12.  
  13.     head('title', translate('user:title', $lang));
  14.     head('description', false);
  15.     head('keywords', false);
  16.     head('robots', 'noindex, nofollow');
  17.  
  18.     $output = layout('standard', compact('banner', 'content'));
  19.  
  20.     return $output;
  21. }
user action begins by building the block of the form. login returns true if the execution of the form has connected the user in which case user does a redirection to the home page.

Note that the identification page has no description and no keywords and that search engines must not index it or follow the links it contains.

  1. function nobody($lang) {
  2.     unset($_SESSION['user']);
  3.  
  4.     $next_page=url('home', $lang);
  5.     header("Location: $next_page");
  6.  
  7.     return false;
  8. }

The nobody action disconnects the user and does a redirection to the home page.

To give access to the actions user and nobody, add an alias in every language for each action in the file includes/aliases.inc:

  1.         'utilisateur'           => 'user',
  2.         'deconnexion'           => 'nobody',
  1.         'user'                  => 'user',
  2.         'disconnect'            => 'nobody',

Add the title of the identification page in the file includes/strings.inc.

In English in the array 'en':

  1.         'user:title'            => 'Identification',

In French in the array 'fr':

  1.         'user:title'            => 'Identification',

Add the view of the identification page in the folders views/en for the English version and views/fr for the French version.

  1. /cms/site14
    1. views
      1. fr
        1. user.phtml
      2. en
        1. user.phtml
  1. <h3>Privileged access</h3>
  2. <?php echo $login; ?>
  1. <h3>Accès privilégié</h3>
  2. <?php echo $login; ?>

The form for identifying a user is in a block. Start by writing the view, first in one language:

  1. /cms/site14
    1. views
      1. en
        1. user.phtml
        2. login.phtml
  1. <div class="form">
  2. <form action="" method="post">
  3. <input type="hidden" name="login_token" value="<?php echo $token; ?>" />
  4. <div class="fields">
  5. <p class="label">What is your connection name?</p>
  6. <p class="input"><input type="text" name="login_login" id="login_login" size="40" maxlength="100" title="Identifier" onkeypress="return focusonenter(event, 'login_password')" value="<?php echo htmlspecialchars($login, ENT_COMPAT, 'UTF-8'); ?>" /></p>
  7. <p class="info">You may also enter your email address.</p>
  8. <p class="label">And your access key?</p>
  9. <p class="input"><input type="password" name="login_password" id="login_password" size="20" maxlength="20" title="Password" onkeypress="return focusonenter(event, 'login_code')" /></p>
  10. <?php if ($with_captcha): ?>
  11. <p class="input">
  12. <img src="<?php echo $base_path; ?>/captcha" alt="" title="Verification code" />
  13. &nbsp;:&nbsp;
  14. <input type="text" name="login_code" id="login_code" size="4" maxlength="4" title="4 letters" onkeypress="return submitonenter(event, 'login_enter')" value="" />
  15. </p>
  16. <?php endif; ?>
  17. <p class="submit"><button type="submit" name="login_enter" id="login_enter">Enter</button></p>
  18. </div>
  19. </form>

The form has 3 input fields: identifier, password and verification code. Apart from $token, 1 variable is necessary: $login. The parameter $with_captcha specifies if a captcha is displayed. All the field names are prefixed with login_. The form has only one button called login_enter.

The rest of the view deals with the error messages. The possible errors are $missing_code, $bad_code, $missing_login, $missing_password, $bad_login, $bad_password and $access_denied.

  1. <?php if ($errors):
  2. extract($errors);
  3. ?>
  4. <div class="errors">
  5. <?php if ($missing_code): ?>
  6. <p>Enter the verification code displayed in the image.</p>
  7. <?php elseif ($bad_code): ?>
  8. <p>The verification code is incorrect.</p>
  9. <?php endif; ?>
  10. <?php if ($missing_login or $missing_password): ?>
  11. <p>Enter your identifier and your password.</p>
  12. <?php elseif ($bad_login): ?>
  13. <p>The identifier is invalid.</p>
  14. <?php elseif ($bad_password): ?>
  15. <p>The password is invalid.</p>
  16. <?php elseif ($access_denied): ?>
  17. <p>Access denied.</p>
  18. <?php endif; ?>
  19. </div>
  20. <?php endif; ?>
  21. </div>

In order to validate the view, write a first version of the login function which is limited to displaying the form:

  1. /cms/site14
    1. blocks
      1. login.php
  1. require_once 'tokenid.php';
  2.  
  3. function login($lang) {
  4.     $login=$password=$code=$token=false;
  5.  
  6.     $missing_code=false;
  7.     $bad_code=false;
  8.  
  9.     $bad_token=false;
  10.  
  11.     $missing_login=false;
  12.     $bad_login=false;
  13.     $missing_password=false;
  14.     $bad_password=false;
  15.     $access_denied=false;
  16.  
  17.     $with_captcha=true;
  18.  
  19.     $_SESSION['login_token'] = $token = token_id();
  20.  
  21.     $errors = compact('missing_code', 'bad_code', 'missing_login', 'bad_login', 'missing_password', 'bad_password', 'access_denied');
  22.  
  23.     $output = view('login', $lang, compact('token', 'with_captcha', 'login', 'errors'));
  24.  
  25.     return $output;
  26. }

Enter http://localhost/cms/site14/en/user in the address bar of your navigator. Edit the login function. Assign values to the input field variables. Set all the error variables to true.

Add the view in French:

  1. /cms/site14
    1. views
      1. fr
        1. user.phtml
        2. login.phtml
  1. <div class="form">
  2. <form action="" method="post">
  3. <input type="hidden" name="login_token" value="<?php echo $token; ?>" />
  4. <div class="fields">
  5. <p class="label">Quel est votre nom de connexion ?</p>
  6. <p class="input"><input type="text" name="login_login" id="login_login" size="40" maxlength="100" title="Identifiant" onkeypress="return focusonenter(event, 'login_password')" value="<?php echo htmlspecialchars($login, ENT_COMPAT, 'UTF-8'); ?>" /></p>
  7. <p class="info">Vous pouvez aussi entrer votre adresse d'email.</p>
  8. <p class="label">Et votre clé d'accès ?</p>
  9. <p class="input"><input type="password" name="login_password" id="login_password" size="20" maxlength="20" title="Mot de passe" onkeypress="return focusonenter(event, 'login_code')" /></p>
  10. <?php if ($with_captcha): ?>
  11. <p class="input">
  12. <img src="<?php echo $base_path; ?>/captcha" alt="" title="Code de vérification" />
  13. &nbsp;:&nbsp;
  14. <input type="text" name="login_code" id="login_code" size="4" maxlength="4" title="4 lettres" onkeypress="return submitonenter(event, 'login_enter')" value="" />
  15. </p>
  16. <?php endif; ?>
  17. <p class="submit"><button type="submit" name="login_enter" id="login_enter">Entrer</button></p>
  18. </div>
  19. </form>
  20. <?php if ($errors):
  21. extract($errors);
  22. ?>
  23. <div class="errors">
  24. <?php if ($missing_code): ?>
  25. <p>Entrez le code de vérification affiché dans l'image.</p>
  26. <?php elseif ($bad_code): ?>
  27. <p>Le code de vérification est incorrect.</p>
  28. <?php endif; ?>
  29. <?php if ($missing_login or $missing_password): ?>
  30. <p>Entrez votre identifiant et votre mot de passe.</p>
  31. <?php elseif ($bad_login): ?>
  32. <p>L'identifiant est invalide.</p>
  33. <?php elseif ($bad_password): ?>
  34. <p>Le mot de passe est invalide.</p>
  35. <?php elseif ($access_denied): ?>
  36. <p>Accès refusé.</p>
  37. <?php endif; ?>
  38. </div>
  39. <?php endif; ?>
  40. </div>

Enter http://localhost/cms/site14/fr/utilisateur in the address bar of your navigator to validate the French version.

Once the views are tuned, complete login with the following code:

  1. require_once 'readarg.php';
  2. require_once 'strflat.php';
  3. require_once 'validateusername.php';
  4. require_once 'validatepassword.php';
  5. require_once 'validatemail.php';
  6. require_once 'tokenid.php';

Loads the code of the functions readarg, strflat, validate_user_name, validate_password, validate_mail and token_id. Add the files validateusername.php and validatepassword.php in the folder library with the following contents:

  1. /cms/site14
    1. library
      1. validateusername.php
      2. validatepassword.php
  1. function validate_user_name($name) {
  2.     return preg_match('/^[a-z]{2,20}$/', $name);
  3. }

validate_user_name returns true if $name contains between 2 and 20 small letters, false otherwise.

  1. function validate_password($s) {
  2.     $regexp='/(?=[a-zA-Z0-9]*?[A-Za-z])(?=[a-zA-Z0-9]*?[0-9])[a-zA-Z0-9]{6,}/';
  3.  
  4.     return preg_match($regexp, $s);
  5. }

validate_password returns true if $password contains at least 6 small or capital letters and digits with at least one digit and one letter, false otherwise.

  1. function login($lang) {
  2.     $action='init';
  3.     if (isset($_POST['login_enter'])) {
  4.         $action='enter';
  5.     }
  6.  
  7.     $login=$password=$code=$token=false;

login starts by initializing $action to 'enter' if the user has pressed the Enter button or 'init' by default if the form is just displayed.

  1.     switch($action) {
  2.         case 'enter':
  3.             if (isset($_POST['login_login'])) {
  4.                 $login=strtolower(strflat(readarg($_POST['login_login'], true)));
  5.             }
  6.             if (isset($_POST['login_password'])) {
  7.                 $password=readarg($_POST['login_password'], true);
  8.             }
  9.             if (isset($_POST['login_code'])) {
  10.                 $code=readarg($_POST['login_code'], true);
  11.             }
  12.             if (isset($_POST['login_token'])) {
  13.                 $token=readarg($_POST['login_token']);
  14.             }
  15.             break;
  16.         default:
  17.             break;
  18.     }

Reads the input fields and filters them with readarg. strtag removes accents from $login which is then converted to small letters.

  1.     $missing_code=false;
  2.     $bad_code=false;
  3.  
  4.     $bad_token=false;
  5.  
  6.     $missing_login=false;
  7.     $bad_login=false;
  8.     $missing_password=false;
  9.     $bad_password=false;
  10.     $access_denied=false;
  11.  
  12.     $with_captcha=true;

Initializes all the error variables before the values sent by the form are validated. $with_captcha decides if a captcha is displayed.

  1.     switch($action) {
  2.         case 'enter':
  3.             if (!isset($_SESSION['login_token']) or $token != $_SESSION['login_token']) {
  4.                 $bad_token=true;
  5.                 break;
  6.             }
  7.  
  8.             if ($with_captcha) {
  9.                 if (!$code) {
  10.                     $missing_code=true;
  11.                     break;
  12.                 }
  13.                 $captcha=isset($_SESSION['captcha']) ? $_SESSION['captcha'] : false;
  14.                 if (!$captcha or $captcha != strtoupper($code)) {
  15.                     $bad_code=true;
  16.                     break;
  17.                 }
  18.             }
  19.  
  20.             if (!$login) {
  21.                 $missing_login=true;
  22.             }
  23.             else if (!validate_user_name($login) and !validate_mail($login)) {
  24.                 $bad_login=true;
  25.             }
  26.             if (!$password) {
  27.                 $missing_password=true;
  28.             }
  29.             else if (!validate_password($password)) {
  30.                 $bad_password = true;
  31.             }
  32.             break;
  33.         default:
  34.             break;
  35.     }

Checks if the captcha matches if $with_captcha is true, controls the token, then validates the values input for the identifier and the password.

  1.     switch($action) {
  2.         case 'enter':
  3.             if ($bad_token or $missing_code or $bad_code or $missing_login or $bad_login or $missing_password or $bad_password) {
  4.                 break;
  5.             }
  6.  
  7.             require_once 'models/user.inc';
  8.  
  9.             $user = user_login($login, $password);
  10.  
  11.             if (!$user) {
  12.                 $access_denied=true;
  13.  
  14.                 require_once 'log.php';
  15.                 write_log('enter.err', substr($login, 0, 40));
  16.  
  17.                 break;
  18.             }
  19.  
  20.             $user['ip'] = client_ip_address();
  21.             $_SESSION['user'] = $user;
  22.  
  23.             unset($_SESSION['login_token']);
  24.  
  25.             return true;
  26.  
  27.         default:
  28.             break;
  29.     }

Checks that no error has been detected, loads the model user.inc and calls the function user_login with the parameters $login and $password. If user_login returns false, the error is logged in enter.err. If the identifier and the password are recognized, the information returned by user_login and the IP address of the client are memorized in the session variable $_SESSION['user'] and login returns true. Remember that in this case, the user actions does a redirection to the home page.

  1.     $_SESSION['login_token'] = $token = token_id();
  2.  
  3.     $errors = compact('missing_code', 'bad_code', 'missing_login', 'bad_login', 'missing_password', 'bad_password', 'access_denied');
  4.  
  5.     $output = view('login', $lang, compact('token', 'with_captcha', 'login', 'errors'));
  6.  
  7.     return $output;
  8. }

If the user has failed to identify, he's disconnected by removing the session variable $_SESSION['user']. The rest of the code prepares all the parameters for the view, including the token which memorized in the session variable $_SESSION['login_token'], before generating the view and returning its content.

Add the folder models directly at the root of the site, the the file user.inc in models with the following content:

  1. /cms/site14
    1. models
      1. user.inc
  1. function user_login($login, $password) {
  2.     if ( ! ($login == 'foobar' and $password == 'f00bar') ) {
  3.         return false;
  4.     }
  5.  
  6.     $now=time();
  7.  
  8.     $user = array();
  9.     $user['id'] = 0;
  10.     $user['name'] = $login;
  11.     $user['access'] = $now;
  12.  
  13.     return $user;
  14. }

user_login returns an array containing all the information on the user's account whose identifier is $login and whose password is $password. If the user isn't recognized, user_login returns false. NOTE: For now the model is reduced to the strict minimum in order to allow tuning the identification page.

Enter http://localhost/cms/site14/en/user and http://localhost/cms/site14/fr/utilisateur to test the form in English and in French. Check that the controls on the input fields are functional. Connect.

To access the identification page, add a link to the banner of the home page:

  1.     $contact=$account=true;
  2.  
  3.     $banner = build('banner', $lang, compact('languages', 'contact', 'account'));

home passes 'account' to true true to banner.

  1.     $menu=$login=$logout=$contact=false;
  2.     $languages=false;
  3.     $nobody_page=$contact_page=false;
  4.  
  5.     $is_identified = user_is_identified();
  6.  
  7.     if ($is_identified) {
  8.         $nobody_page=url('nobody', $lang);
  9.         $logout = true;
  10.     }
  11.  
  12.     if ($components) {
  13.         foreach ($components as $v => $param) {
  14.             switch ($v) {
  15.                 case 'account':
  16.                     if ($param) {
  17.                         if (!$is_identified) {
  18.                             $user_page=url('user', $lang);
  19.                             $login = true;
  20.                         }
  21.                     }
  22.                     break;

In all cases, if the user is identified, banner adds a link to the disconnect page. If $components contains 'account' to true true and if the user is not identified, banner adds a link to the identification page.

  1.     if ($logout or $contact) {
  2.         $menu = view('bannermenu', $lang, compact('contact', 'contact_page', 'logout', 'nobody_page', 'login', 'user_page'));
  3.     }

Add the file userisidentified.php in the folder library with the following content:

  1. /cms/site14
    1. library
      1. userisidentified.php
  1. function user_is_identified() {
  2.     return isset($_SESSION['user']);
  3. }

user_is_identified returns true if the user's description is in the session.

Add the links to the menu in the banner in the files views/en/bannermenu.phtml and views/fr/bannermenu.phtml.

  1. <?php if (isset($logout) and $logout): ?>
  2. <li><a id="exit" href="<?php echo $nobody_page; ?>" title="Disconnect"><span>Disconnect</span></a></li>
  3. <?php endif; ?>
  4. <?php if (isset($login) and $login): ?>
  5. <li><a id="enter" href="<?php echo $user_page; ?>" title="Your account"><span>Account</span></a></li>
  6. <?php endif; ?>
  1. <?php if (isset($logout) and $logout): ?>
  2. <li><a id="exit" href="<?php echo $nobody_page; ?>" title="Déconnexion"><span>Déconnexion</span></a></li>
  3. <?php endif; ?>
  4. <?php if (isset($login) and $login): ?>
  5. <li><a id="enter" href="<?php echo $user_page; ?>" title="Votre compte"><span>Compte</span></a></li>
  6. <?php endif; ?>

Modify the style sheet to display buttons instead of links:

  1. #bannermenu {width:160px;float:left;margin-top:13px;margin-left:40px;}
  2. #bannermenu #mail {width:24px;height:24px;float:right;margin-left:6px;background:transparent url(../buttons/mail.png) no-repeat center center;}
  3. #bannermenu #exit {width:24px;height:24px;float:left;margin-right:6px;background:transparent url(../buttons/cancel.png) no-repeat center center;}
  4. #bannermenu #enter {width:24px;height:24px;float:right;margin-right:6px;background:transparent url(../buttons/user.png) no-repeat center center;}

Copy the icons in the folder buttons:

  1. /cms/site14
    1. buttons
      1. cancel.png
      2. user.png

Enter http://localhost/cms/site14 in the address bar of your navigator. Go the identification page. Display the source code. Disable the CSS to evaluate the quality of the generated document. Fill in the form validating each field with Enter. Check that the Disconnect button is properly displayed on the contact page. Disconnect.

Comments

To add a comment, click here.