Identifying users
Create site14 by copying site13.
- /cms
- ...
- site13
- 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:
- /cms/site14
- actions
- user.php
- nobody.php
- actions
- function user($lang) {
- $login = build('login', $lang);
- if (true === $login) {
- $next_page = url('home', $lang);
- header("Location: $next_page");
- return false;
- }
- $banner = build('banner', $lang);
- $content = view('user', $lang, compact('login'));
- head('title', translate('user:title', $lang));
- head('description', false);
- head('keywords', false);
- head('robots', 'noindex, nofollow');
- $output = layout('standard', compact('banner', 'content'));
- return $output;
- }
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.
- function nobody($lang) {
- unset($_SESSION['user']);
- $next_page=url('home', $lang);
- header("Location: $next_page");
- return false;
- }
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:
- 'utilisateur' => 'user',
- 'deconnexion' => 'nobody',
- 'user' => 'user',
- 'disconnect' => 'nobody',
Add the title of the identification page in the file includes/strings.inc.
In English in the array 'en':
- 'user:title' => 'Identification',
In French in the array 'fr':
- '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.
- /cms/site14
- views
- fr
- user.phtml
- en
- user.phtml
- fr
- views
- <h3>Privileged access</h3>
- <?php echo $login; ?>
- <h3>Accès privilégié</h3>
- <?php echo $login; ?>
The form for identifying a user is in a block. Start by writing the view, first in one language:
- /cms/site14
- views
- en
- user.phtml
- login.phtml
- en
- views
- <div class="form">
- <form action="" method="post">
- <input type="hidden" name="login_token" value="<?php echo $token; ?>" />
- <div class="fields">
- <p class="label">What is your connection name?</p>
- <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>
- <p class="info">You may also enter your email address.</p>
- <p class="label">And your access key?</p>
- <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>
- <?php if ($with_captcha): ?>
- <p class="input">
- <img src="<?php echo $base_path; ?>/captcha" alt="" title="Verification code" />
- :
- <input type="text" name="login_code" id="login_code" size="4" maxlength="4" title="4 letters" onkeypress="return submitonenter(event, 'login_enter')" value="" />
- </p>
- <?php endif; ?>
- <p class="submit"><button type="submit" name="login_enter" id="login_enter">Enter</button></p>
- </div>
- </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
.
- <?php if ($errors):
- extract($errors);
- ?>
- <div class="errors">
- <?php if ($missing_code): ?>
- <p>Enter the verification code displayed in the image.</p>
- <?php elseif ($bad_code): ?>
- <p>The verification code is incorrect.</p>
- <?php endif; ?>
- <?php if ($missing_login or $missing_password): ?>
- <p>Enter your identifier and your password.</p>
- <?php elseif ($bad_login): ?>
- <p>The identifier is invalid.</p>
- <?php elseif ($bad_password): ?>
- <p>The password is invalid.</p>
- <?php elseif ($access_denied): ?>
- <p>Access denied.</p>
- <?php endif; ?>
- </div>
- <?php endif; ?>
- </div>
In order to validate the view, write a first version of the login
function which is limited to displaying the form:
- /cms/site14
- blocks
- login.php
- blocks
- require_once 'tokenid.php';
- function login($lang) {
- $login=$password=$code=$token=false;
- $missing_code=false;
- $bad_code=false;
- $bad_token=false;
- $missing_login=false;
- $bad_login=false;
- $missing_password=false;
- $bad_password=false;
- $access_denied=false;
- $with_captcha=true;
- $_SESSION['login_token'] = $token = token_id();
- $errors = compact('missing_code', 'bad_code', 'missing_login', 'bad_login', 'missing_password', 'bad_password', 'access_denied');
- $output = view('login', $lang, compact('token', 'with_captcha', 'login', 'errors'));
- return $output;
- }
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:
- /cms/site14
- views
- fr
- user.phtml
- login.phtml
- fr
- views
- <div class="form">
- <form action="" method="post">
- <input type="hidden" name="login_token" value="<?php echo $token; ?>" />
- <div class="fields">
- <p class="label">Quel est votre nom de connexion ?</p>
- <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>
- <p class="info">Vous pouvez aussi entrer votre adresse d'email.</p>
- <p class="label">Et votre clé d'accès ?</p>
- <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>
- <?php if ($with_captcha): ?>
- <p class="input">
- <img src="<?php echo $base_path; ?>/captcha" alt="" title="Code de vérification" />
- :
- <input type="text" name="login_code" id="login_code" size="4" maxlength="4" title="4 lettres" onkeypress="return submitonenter(event, 'login_enter')" value="" />
- </p>
- <?php endif; ?>
- <p class="submit"><button type="submit" name="login_enter" id="login_enter">Entrer</button></p>
- </div>
- </form>
- <?php if ($errors):
- extract($errors);
- ?>
- <div class="errors">
- <?php if ($missing_code): ?>
- <p>Entrez le code de vérification affiché dans l'image.</p>
- <?php elseif ($bad_code): ?>
- <p>Le code de vérification est incorrect.</p>
- <?php endif; ?>
- <?php if ($missing_login or $missing_password): ?>
- <p>Entrez votre identifiant et votre mot de passe.</p>
- <?php elseif ($bad_login): ?>
- <p>L'identifiant est invalide.</p>
- <?php elseif ($bad_password): ?>
- <p>Le mot de passe est invalide.</p>
- <?php elseif ($access_denied): ?>
- <p>Accès refusé.</p>
- <?php endif; ?>
- </div>
- <?php endif; ?>
- </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:
- require_once 'readarg.php';
- require_once 'strflat.php';
- require_once 'validateusername.php';
- require_once 'validatepassword.php';
- require_once 'validatemail.php';
- 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:
- /cms/site14
- library
- validateusername.php
- validatepassword.php
- library
- function validate_user_name($name) {
- return preg_match('/^[a-z]{2,20}$/', $name);
- }
validate_user_name
returns true
if $name
contains between 2 and 20 small letters, false
otherwise.
- function validate_password($s) {
- $regexp='/(?=[a-zA-Z0-9]*?[A-Za-z])(?=[a-zA-Z0-9]*?[0-9])[a-zA-Z0-9]{6,}/';
- return preg_match($regexp, $s);
- }
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.
- function login($lang) {
- $action='init';
- if (isset($_POST['login_enter'])) {
- $action='enter';
- }
- $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.
- switch($action) {
- case 'enter':
- if (isset($_POST['login_login'])) {
- $login=strtolower(strflat(readarg($_POST['login_login'], true)));
- }
- if (isset($_POST['login_password'])) {
- $password=readarg($_POST['login_password'], true);
- }
- if (isset($_POST['login_code'])) {
- $code=readarg($_POST['login_code'], true);
- }
- if (isset($_POST['login_token'])) {
- $token=readarg($_POST['login_token']);
- }
- break;
- default:
- break;
- }
Reads the input fields and filters them with readarg
. strtag
removes accents from $login
which is then converted to small letters.
- $missing_code=false;
- $bad_code=false;
- $bad_token=false;
- $missing_login=false;
- $bad_login=false;
- $missing_password=false;
- $bad_password=false;
- $access_denied=false;
- $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.
- switch($action) {
- case 'enter':
- if (!isset($_SESSION['login_token']) or $token != $_SESSION['login_token']) {
- $bad_token=true;
- break;
- }
- if ($with_captcha) {
- if (!$code) {
- $missing_code=true;
- break;
- }
- $captcha=isset($_SESSION['captcha']) ? $_SESSION['captcha'] : false;
- if (!$captcha or $captcha != strtoupper($code)) {
- $bad_code=true;
- break;
- }
- }
- if (!$login) {
- $missing_login=true;
- }
- else if (!validate_user_name($login) and !validate_mail($login)) {
- $bad_login=true;
- }
- if (!$password) {
- $missing_password=true;
- }
- else if (!validate_password($password)) {
- $bad_password = true;
- }
- break;
- default:
- break;
- }
Checks if the captcha matches if $with_captcha
is true
, controls the token, then validates the values input for the identifier and the password.
- switch($action) {
- case 'enter':
- if ($bad_token or $missing_code or $bad_code or $missing_login or $bad_login or $missing_password or $bad_password) {
- break;
- }
- require_once 'models/user.inc';
- $user = user_login($login, $password);
- if (!$user) {
- $access_denied=true;
- require_once 'log.php';
- write_log('enter.err', substr($login, 0, 40));
- break;
- }
- $user['ip'] = client_ip_address();
- $_SESSION['user'] = $user;
- unset($_SESSION['login_token']);
- return true;
- default:
- break;
- }
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.
- $_SESSION['login_token'] = $token = token_id();
- $errors = compact('missing_code', 'bad_code', 'missing_login', 'bad_login', 'missing_password', 'bad_password', 'access_denied');
- $output = view('login', $lang, compact('token', 'with_captcha', 'login', 'errors'));
- return $output;
- }
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:
- /cms/site14
- models
- user.inc
- models
- function user_login($login, $password) {
- if ( ! ($login == 'foobar' and $password == 'f00bar') ) {
- return false;
- }
- $now=time();
- $user = array();
- $user['id'] = 0;
- $user['name'] = $login;
- $user['access'] = $now;
- return $user;
- }
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:
- $contact=$account=true;
- $banner = build('banner', $lang, compact('languages', 'contact', 'account'));
home
passes 'account'
to true true
to banner
.
- $menu=$login=$logout=$contact=false;
- $languages=false;
- $user_page=$nobody_page=$contact_page=false;
- $is_identified = user_is_identified();
- if ($is_identified) {
- $nobody_page=url('nobody', $lang);
- $logout = true;
- }
- if ($components) {
- foreach ($components as $v => $param) {
- switch ($v) {
- case 'account':
- if ($param) {
- if (!$is_identified) {
- $user_page=url('user', $lang);
- $login = true;
- }
- }
- 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.
- if ($logout or $contact) {
- $menu = view('bannermenu', $lang, compact('contact', 'contact_page', 'logout', 'nobody_page', 'login', 'user_page'));
- }
Add the file userisidentified.php in the folder library with the following content:
- /cms/site14
- library
- userisidentified.php
- library
- function user_is_identified() {
- return isset($_SESSION['user']);
- }
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.
- <?php if (isset($logout) and $logout): ?>
- <li><a id="exit" href="<?php echo $nobody_page; ?>" title="Disconnect"><span>Disconnect</span></a></li>
- <?php endif; ?>
- <?php if (isset($login) and $login): ?>
- <li><a id="enter" href="<?php echo $user_page; ?>" title="Your account"><span>Account</span></a></li>
- <?php endif; ?>
- <?php if (isset($logout) and $logout): ?>
- <li><a id="exit" href="<?php echo $nobody_page; ?>" title="Déconnexion"><span>Déconnexion</span></a></li>
- <?php endif; ?>
- <?php if (isset($login) and $login): ?>
- <li><a id="enter" href="<?php echo $user_page; ?>" title="Votre compte"><span>Compte</span></a></li>
- <?php endif; ?>
Modify the style sheet to display buttons instead of links:
- #bannermenu {width:160px;float:left;margin-top:13px;margin-left:40px;}
- #bannermenu #mail {width:24px;height:24px;float:right;margin-left:6px;background:transparent url(../buttons/mail.png) no-repeat center center;}
- #bannermenu #exit {width:24px;height:24px;float:left;margin-right:6px;background:transparent url(../buttons/cancel.png) no-repeat center center;}
- #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:
- /cms/site14
- buttons
- cancel.png
- user.png
- buttons
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