*/ ?>
Gestion de sessions utilisateurs

Dans ce tutoriel, vous allez apprendre à suivre une session d'un utilisateur.

Contexte du tutoriel:

Les exemples ci-dessous sont un prolongement du tutoriel "Liste dynamique" ; si vous ne l'avez pas encore lu, nous vous invitons à en prendre connaissance maintenant.

Dans le précédent tuto, vous avez appris à générer une liste d'articles à la volée.
N'importe quel utilisateur qui se connecte voit la liste, à jour en temps réel, des articles dans la base de données.

Nous souhaitons maintenant qu'un utilisateur puisse ajouter des articles dans un panier.
Cela suppose que le serveur puisse identifier l'utilisateur qui appelle un script, comme par exemple pour:

Prérequis

Pour commencer, lire l'article précédent: "Liste dynamique".

Pour faire fonctionner l'exemple de ce tutoriel, vous devez disposer d'un serveur web, du langage php et d'une base de données MySQL. Des compétences minimales en php et sql sont nécessaires.

Moyennant quelques adaptations, vous pouvez tester cet exemple avec une autre base et/ou un autre langage: la seule contrainte est de produire des documents XML conformes aux spécifications AppMobile.

Si vous n'avez pas de serveur à disposition, des offres d'essai gratuites existent chez de nombreux hébergeurs - une simple recherche du type "hébergement gratuit mysql php" sur votre moteur de recherche préféré suffira à vous apporter une solution.

Principe

Lorsqu'un utilisateur lance une application AppMobile, un identifiant unique lui est attribué. Cet identifiant sera valable pour toute la durée de la session, c'est à dire tant que l'application n'aura pas été quittée ("killed").

L'application transmet cet identifiant en paramètre à chaque URL qu'elle appelle: page HTML, liste XML, flux RSS, scripts paramétrés dans les modules AppMobile...
Il suffit alors de récupérer ce paramètre, côté serveur, pour identifier la session et faire le lien avec les données du compte utilisateur correspondant.

Identifier la session

L'identifiant de cession est passé dans l'URL (paramètre "GET"):
http://myurl.com?id_mvsappmobile=xxx.

Il suffit de récupérer la valeur de cet identifiant pour l'exploiter ensuite.
Par exemple en PHP, la valeur de l'id peut être récupérée ainsi:

<?php
$userid = $_GET['id_mvsappmobile'];
?>

Lier à un compte utilisateur

Toute URL appelée par une instance donnée de l'application (schématiquement, toute connexion depuis un même téléphone) enverra le même identifiant.

Il suffit donc de gérer trois étapes:

Connexion d'un utilisateur

La connexion d'un utilisateur repose sur l'envoi par l'appli d'un login et d'un mot de passe à un script d'identification.

Saisie des identifiants

La saisie de ses identifiants est demandée à l'utilisateur:

Envoi des identifiants

Que vous ayez choisi l'écran de connexion natif ou un formulaire intégré dans une page HTML, les données saisie par l'utilisateur vont être envoyées par l'application à votre script de connexion.
L'URL de ce script de connexion est celle que vous aurez indiquée:

Traitement de la connexion

Quand l'utilisateur valide ses identifiants de connexion, l'application appelle l'URL de connexion avec les paramètres suivants (paramètres passés en POST):

Le script appelé doit réaliser deux actions: d'abord vérifier qu'il existe un utilisateur dont le login et le mot de passe correspondent, puis lier cet utilisateur à l'identifiant de session

A partir de là, tout script appelé avec cet identifiant de session pourra être rattaché à l'utilisateur correspondant (cf point "contrôle d'accès" ci-dessous), et ce jusqu'à ce que l'utilisateur se déconnecte.

Table des utilisateurs

Les informations à stocker pour suivre les connexions sont: un login, un mot de passe (stocké de préférence crypté), et l'identification de la session active.
Table d'utilisateurs

Vous pouvez stocker d'autres informations complémentaires selon l'objet de votre application.
Cette table peut être l'endroit où stocker, par exemple: nom, adresse, téléphone... ou alors simplement l'id de la fiche client (ou fournisseur, salarié, abonné, adhérent...) correspondant à cet utilisateur pour établir une liaison avec d'autres tables existant déjà dans votre base.
Un champ relatif au profil de l'utilisateur (user, agent, admin...) permettra par exemple de délivrer différents niveaux d'informations, ou de désactiver certaines fonctionnalités, selon la catégorie de l'utilisateur.

Nota: dans l'exemple ci-dessus, une clé primaire distincte de l'identifiant a été créée. Nous vous conseillons de toujours procéder ainsi pour de nombreuses raisons qui sortent du cadre de ce tutoriel. Notamment: optimisation de la base de données (pour les liens depuis d'autres tables, seul un id numérique sera dupliqué, et pas le login complet de l'utilisateur), souplesse (un changement de login d'un utilisateur sera possible sans difficulté même s'il existe de nombreuses relations avec plusieurs autres tables), etc.

Cette clé primaire sera notamment utilisée dans le tutoriel "panier" pour établir une relation entre un utilisateur et les articles qu'il aura sélectionnés.

Pour tester les scripts de connexion/contrôle/déconnexion de ce tuto, vous pouvez créer la table d'exemple illustrée ci-dessus avec ces requêtes MySQL (notez que les mots de passe sont cryptés en SHA1):

CREATE TABLE IF NOT EXISTS `amdoc_users` (
  `id_user` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `log` varchar(20) NOT NULL,
  `pass` varchar(40) NOT NULL,
  `id_mvsappmobile` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id_user`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;
INSERT INTO `amdoc_users` (`log`, `pass`, `id_mvsappmobile`) VALUES
('Bob', SHA1('MyPass3'), 'disconnected'),
('Tom', SHA1('PassWord'), 'disconnected'),
('Rob', SHA1('PsWd324'), 'disconnected'),
('Sam', SHA1('123'), 'disconnected'),
('Jim', SHA1('azerty'), 'disconnected');

Contrôle des identifiants et connexion

L'utilisateur a saisi un mot de passe et un identifiant, dans un formulaire ou dans le module interne paramétré comme indiqué plus haut pour appeler l'URL:
http://mysite.com/login.php

Voici un exemple de script de connexion:

Attention: nous avons décidé d'utiliser les anciennes fonctions mysql_* dans les scripts de démo, pour une compatibilité plus large. Vous ne devriez PAS les utiliser en production, mais télécharger les scripts des modules téléchargeables à la place (basés sur la librairie PDO, plus complets et plus sûrs).

<?php
/* Functional demo... Messages and layouts have to be customized! */
include('config.php');//contains data like db connection IDs, base URL, etc.
$login=$_POST['login']; //read parameters -> session id
$passw=sha1($_POST['mdp']); //-> password (crypted like it is stored in db)
$id_mvs=$_POST['id_mvsappmobile']; //-> session id
$db_tbl_user="amdoc_users";
$db =  @mysql_connect($dbhost, $dbuser, $dbpassword);
if(!$db) { echo("ConnectProblem");exit; } //Problème connexion serveur MySQL
if(!@mysql_select_db($dbname)) { echo("DBOpenProblem");exit; } //MySQL connection problem
//Does a user exist with these log/pass? 
$query="select * from $db_tbl_user where `log`='$login' and `pass`='$passw'";
$result = mysql_query($query,$db);
if(!$result) { echo("DBDataProblem");exit; } //MySQL problem
else if(mysql_num_rows($result)==0) { echo("ConnectRefused");exit; } //unexisting user or bad password
else {//there is a user with these log/pass
	//Step 1: disconnect eventual already connected user on this session id. Never 2 users for 1 session.
	$query="update $db_tbl_user set `id_mvsappmobile`='disconnected' where `id_mvsappmobile`='$id_mvs'";
	$result = mysql_query($query,$db);
	if(!$result) { echo("DBDataProblem");exit; } //MySQL problem
	else {
			//Step 2: connect user to account
			$query="update $db_tbl_user set `id_mvsappmobile`='$id_mvs' where `log`='$login' and `pass`='$passw'";
			$result = mysql_query($query,$db);
			if(!$result) { echo("DBDataProblem");exit; } //MySQL problem
	}
}
mysql_close($db);
echo("ok");exit;
?>

Toutes explications utiles sont insérées sous forme de commentaires dans le script.

Nota 1. Ce script gère les cas d'erreurs de façon simpliste mais efficace: il retourne "ok" si l'utilisateur a bien été connecté, et un mot clé décrivant l'erreur dans tous les autres cas.
  • Si vous utilisez le module de connexion intégré, modifiez les valeurs de retour: "1" en cas de succès, "0" en cas de mauvais identifiant ou mot de passe, "-1" pour toute autre cas d'échec.
  • Si vous préférez intégrer votre propre formulaire de connexion dans une page HTML, vous devrez remplacer les "return 0" ou "return 1" par l'affichage d'une page d'erreur ou, au contraire, une page de bienvenue (par exemple le tableau de bord de l'utilisateur, ses derniers achats/messages/etc.).
  • Si les données du formulaire, intégré dans une page HTML, sont postées en AJAX, à vous de traiter en javascript la valeur retournée par le script.
Nota 2. Si vous avez activé le cryptage dans le module intégré (section "modules optionnels" du portail mvsappmobile.com), l'exemple ci-dessus ne fonctionnera pas car le mot de passe reçu par le script serait crypté deux fois, voir dans les premières lignes de code: $passw=sha1($_POST['mdp']);. Supprimez un des deux cryptages.

Contrôle d'accès

Exemple d'une messagerie

Prenons l'exemple d'une application avec une fonction "messagerie" (un autre exemple est traité dans un tutoriel spécifique: la gestion d'un panier).

Nous voulons écrire le script qui crée la liste des messages reçus par l'utilisateur (pour plus d'infos sur les listes XML, voir tutoriel "Listes dynamiques").

La réalisation de cette liste se résume à:

  • récupérer le paramètre "id_mvsappmobile" passé par l'application au script
  • vérifier si cette session a été liée à un compte utilisateur (l'identifiant de session reçu en paramètre est-il enregistré dans la base en face d'une compte utilisateur?)
    • si non, retourner un item "Merci de vous connecter"
    • si oui, chercher les messages correspondant à ce compte utilisateur
      • si des messages sont trouvés, les lister
      • si aucun message n'existe, retourner un item "Vous n'avez aucun message"

Données

La messagerie repose sur la table suivante.
. id_from et id_dest permettent de lier cette table avec celle des comptes utilisateurs (la table "amdoc_users" présentée plus haut).
. opened sert à marquer les messages déjà lus, pour permettre une mise en évidence des nouveaux messages à l'affichage.
. deleted permet de gérer une suppression temporaire de messages (corbeille) plutôt qu'une suppression irréversible.

Table d'utilisateurs

Les requêtes pour créer la table et y ajouter quelques messages:

CREATE TABLE IF NOT EXISTS `amdoc_messages` (
  `id_msg` int(11) NOT NULL AUTO_INCREMENT,
  `subj` varchar(150) DEFAULT '',
  `id_from` int(11) DEFAULT '0',
  `id_dest` varchar(50) DEFAULT '0',
  `date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `text` varchar(1000) DEFAULT '',
  `opened` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'nonzero if opened',
  `deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'nonzero if deleted',
  PRIMARY KEY (`id_msg`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
INSERT INTO `amdoc_messages` (`id_msg`, `subj`, `id_from`, `id_dest`, `date`, `text`, `opened`, `deleted`) VALUES
(1, 'Just a test', 3, '4', '2014-07-22 12:25:09', 'Hi,\r\nThis is my first message, just a test, tell me if it works!', 1, 0),
(2, 'Got it', 4, '3', '2014-07-22 13:41:26', 'Hello,\r\nJust got your message,\r\nWorks great ;-)', 1, 0),
(3, 'Thanks', 3, '4', '2014-07-22 14:46:07', 'ok, thanks', 0, 0);

Requête

Sur la base de ces données, nous pouvons nous attaquer au script qui crée la liste des messages. Ce script ne présente aucune difficulté, seule la requête SQL mérite quelques précisions.

Dans la table des messages, chaque entrée est lié à un expéditeur (clé id_from) et à un destinataire (clé id_dest) qui font référence à deux entrées de la même table des utilisateurs.
C'est pourquoi nous devons faire deux jointures entre la table des messages et la table des utilisateurs. Décomposons:

  • une première fois (FROM `$db_tbl_msg` m, `$db_tbl_user` t, où "t" signifie "to") pour identifier l'utilisateur connecté(WHERE t.id_mvsappmobile='$id_mvs'), filtrer les messages adressés à cet utilisateur and m.id_dest=t.id_user et récupérer les données de ces messages (SELECT m.id_msg, m.subj, m.`date`, m.opened
  • une deuxième fois (FROM `$db_tbl_msg` m, `$db_tbl_user` f, où "f" signifie "from") pour identifier l'expéditeur (WHERE f.id_user=m.id_from) et récupérer son nom (SELECT f.log as exp).

Il reste à filtrer les messages qui ne sont pas dans la corbeille (and m.deleted=0)... et à tout remettre dans l'ordre:

SELECT m.id_msg, m.subj, m.`date`, m.opened, f.log as exp
FROM `$db_tbl_msg` m, `$db_tbl_user` t,`$db_tbl_user` f
WHERE t.id_mvsappmobile='$id_mvs' and m.id_dest=t.id_user and f.id_user=m.id_from and m.deleted=0

Script: liste des messages

<?php
include('config.php');//données de config, notamment $dbhost,$dbuser,$dbpassword,$dbname pour accéder à la base 
$id_mvs=$_GET['id_mvsappmobile']; //récupération del'id de session passé en paramètre
$xml="<?xml version='1.0' encoding='UTF-8'?>\r\n<items version='1.0' type='1' reload='1' locate='0'>";//le XML de la liste à afficher
header ("Content-Type:text/xml; charset=utf-8");//entête http
function add_item(&$xmlstring, $typ, $img, $tx1, $tx2, $tx3, $link) {
	$xmlstring.="
	<item type='$typ'> 
		<img>$urlprefix/$img</img>
		<txt1>$tx1</txt1>
		<txt2>$tx2</txt2>    
		<txt3>$tx3</txt3>
		<link type='1' url='$link' title='$tx1' />
	</item>";
}
function quit_script(&$xmlstring, $errorMsg, $pic) {
	add_item($xmlstring, 1, $pic, $errorMsg, "", "", "");
	echo($xmlstring."\r\n</items>");
	exit;
}
$db =  @mysql_connect($dbhost, $dbuser, $dbpassword);
if(!$db) { quit_script($xml,"Pas de connexion","pic/noConnec.png"); } //Problème connexion serveur MySQL
if(!@mysql_select_db($dbname)) { quit_script($xml,"Pas de connexion","pic/noConnec.png"); } //Problème sélection base MySQL
$query="select id_user from $db_tbl_user where id_mvsappmobile='$id_mvs'";
$result = @mysql_query($query,$db);
if (!$result) { quit_script($xml,"Pas de connexion","pic/noConnec.png"); } //Problème MySQL
else {//data was found
	if(mysql_num_rows($result)!=1) { quit_script($xml,"Merci de vous identifier","pic/noSession.png"); }//no connected user for this session
	else {//user is connected
		$query = "SELECT m.id_msg, m.subj, m.`date`, m.opened, f.log as exp
		FROM `$db_tbl_msg` m, `$db_tbl_user` t,`$db_tbl_user` f 
		WHERE t.id_mvsappmobile='$id_mvs' and m.id_dest=t.id_user and f.id_user=m.id_from and m.deleted=0";
		$result = @mysql_query($query,$db);
		@mysql_close($db);
		if(!$result) { quit_script($xml,"Pas de connexion","pic/noConnec.png"); } //Problème MySQL
		else if (mysql_num_rows($result) == 0) { add_item($xml, 1, "pic/noMsg.png", "Aucun message", "", "", ""); }//there's no message
			else {//there are messages
				while ($l = mysql_fetch_assoc($result)) {
					$im=($l['opened']=="1" ? "pic/openedMsg.png" : "pic/newMsg.png");
					add_item($xml, 1, $im, $l['subj'], "From: ".$l['exp'], date('d/m/Y',strtotime($l['date'])), "$urlprefix/msg_show.php?msg=".$l['id_msg']."&id_mvsappmobile=$id_mvs");
				}
			}
		echo ($xml."\r\n</items>");
	}
}
?>

Déconnexion

Déconnecter un utilisateur revient à couper le lien entre son id de session et son compte.

update `amdoc_users` set `id_mvsappmobile`='disconnected'
where `id_mvsappmobile`='{id de session}'

C'est tout...

<?php
/* Functional demo... Messages and layouts have to be customized! */
include('config.php');//contains data like db connection IDs, base URL, etc.
$id_mvs=$_GET['id_mvsappmobile']; //read parameters -> session id
$db =  @mysql_connect($dbhost, $dbuser, $dbpassword);
if(!$db) { echo("ConnectProblem");exit; } //MySQL connection problem
if(!@mysql_select_db($dbname)) { echo("DBOpenProblem");exit; } //MySQL db selection problem
//déconnexion 
$query="update $db_tbl_user set `id_mvsappmobile`='disconnected' where `id_mvsappmobile`='$id_mvs'";
$result = mysql_query($query,$db);
if(!$result) { echo("DBDataProblem");exit; } //MySQL problem
$nbrows=mysql_affected_rows($db);
mysql_close($db);
if($nbrows==0) { echo("UnexistingSession");exit; } //MySQL problem
else if($nbrows>1) { echo("SeveralSessionsDisconnected");exit; } //This should never happen.
//The login script must refuse connection of new user on existing session id, OR delete existing connection before connecting new.
else { echo("DisconnectOk"); }
?>