Jérôme Cutrona

Version statique de l'intranet de Jérôme Cutrona - Rejoindre la version dynamique 🔒

Les exemples et corrections sont potentiellement non fonctionnels.

B.U.T. Informatique - IUT de Reims - Université de Reims

Authentification et sessions

Navigation

Objectifs de la séance

  • Utiliser phpMyAdmin pour créer et manipuler une table
  • Écrire une classe PHP simple
  • Effectuer une authentification classique
  • Utiliser les données de session pour mémoriser un objet PHP
  • Simuler un mode client/serveur connecté à l'aide des sessions
  • Limiter l'accès de certaines pages en modifiant le code de réponse HTTP avec « header() » et l'en-tête HTTP « Location »
  • Utiliser les sessions pour y stocker un objet PHP
  • Écrire une classe abstraite et la dériver
  • Effectuer une authentification sécurisée

Introduction

L'objectif est de créer une classe « User » qui permette de manipuler en lecture des enregistrements d'une table « user » MySQL.

La classe est basée sur la table « user » MySQL dans laquelle le mot de passe sera stocké sous la forme d'un condensat SHA512.

Plus précisément, les données de cette table seront utilisées pour valider l'authenticité d'un utilisateur, dans un premier temps par une méthode d'authentification classique, puis selon une méthode empêchant la capture du couple login et mot de passe et supprimant la possibilité de rejouer les requêtes HTTP.

L'authentification de l'utilisateur sera réalisée par les classes « UserAuthentication » pour l'authentification classique.

Les étudiants les plus avancés pourront développer « SecureUserAuthentication » pour l'authentification sécurisée.

À l'image des développements autour des données de session, vous utiliserez un projet Composer et organiserez vos fichiers selon la séparation classique entre les fichiers sources (classes contenues dans le répertoire « src/ » selon la même sous-arborescence que les « namespace ») et les programmes (localisés dans « public/ » avec les ressources statiques comme les fichiers CSS). Vous profiterez également de l'auto-chargement géré par Composer. Puisque la structure est quasi identique au précédent projet, il vous est proposé de travailler à partir d'une divergence (« fork ») d'un projet GitLab dont la structure et le contenu vous permettent de gagner du temps.

Quelques considérations liées à la configuration système de l'IUT

Information

Si vous travaillez sur votre ordinateur personnel, sur Linux ou sur Windows, vous pouvez placer le répertoire « php-authentication » du projet où bon vous semble. La partie qui suit concerne uniquement le travail sur les ordinateurs de l'IUT. Cela ne vous empêche pas d'en prendre connaissance.

Votre répertoire d'accueil « $HOME » se trouve sur un emplacement réseau NFS, ce qui vous permet de retrouver vos fichiers quel que soit le PC du département sur lequel vous vous connectez. PhpStorm surveille en permanence les modifications des fichiers de votre projet et les enregistre automatiquement. Ce principe est très pratique mais fonctionne très mal sur un système de fichiers en NFS. De plus, les outils d'analyse statique de code comme PHP CS Fixer sont très gourmands en ressources et ne peuvent pas fonctionner correctement sur un système de fichiers en NFS. Afin d'améliorer les performances de votre poste de travail et d'éviter de saturer le serveur de fichiers du département informatique, vous allez travailler dans le répertoire « /working/votre_login » de la machine sur laquelle vous êtes connecté.

Versionnage du projet

Pour ce sujet de TP, vous créerez une divergence (« fork ») d'un dépôt Git existant et créerez un clone local dans « /working/votre_login ». Vous réaliserez une opération de validation avec git commit au moins une fois par question du sujet.

Créer une divergence (« Fork ») du dépôt Gitlab distant et clonage local

  • Accédez au dépôt GitLab « cutron01/php-authentication »
  • Créer une divergence privée (« fork ») de ce dépôt distant dans le « namespace GitLab » correspondant à votre login
  • Accordez la permission « Reporter » à votre enseignant de TP/TD
  • Ouvrez un terminal puis utilisez la commande cd un_chemin_relatif_ou_absolu pour vous placer dans le répertoire « /working/votre_login »
  • Utilisez la commande suivante pour cloner localement le nouveau dépôt distant :
    git clone https://iut-info.univ-reims.fr/gitlab/votre_login/php-authentication
  • Utilisez la commande cd un_chemin_relatif_ou_absolu pour vous placer dans le répertoire créé
  • Éditez le fichier README.md dans lequel doivent figurer :
    • un titre de niveau 1 contenant le titre explicite du projet
    • un titre de niveau 2 « Auteur(s) » vous permettant de préciser votre nom et votre prénom (ainsi que ceux de votre binôme, le cas échéant)
    • un titre de niveau 2 « Installation / Configuration » précisant les points essentiels permettant d'installer, de configurer et de lancer le projet
  • Ajoutez les fichiers au dépôt
    git add .
  • Effectuez une nouvelle validation
    git commit -m "Repository setup"
  • Poussez le dépôt local vers le dépôt distant
    git push -u origin main

Consignes

  • Vous effectuerez une validation après chaque question.
  • À la fin de chaque séance, vous effectuerez une validation. Si cette validation contient du code incomplet ou ne fonctionnant pas, mentionnez-le dans le message de validation. Vous pousserez ensuite votre travail vers le dépôt distant.
  • Ce dépôt sera utilisé par votre enseignant(e) de TP pour évaluer votre travail, que cela conduise à un note ou pas. Assurez-vous donc régulièrement que tous les fichiers que vous souhaitez lui rendre sont bien présents dans le dépôt. Respectez les noms des fichiers qui vous sont demandés si des consignes particulières vous sont données.
  • Le dépôt lui-même sera évalué, soignez l'écriture de vos messages : clarté, pertinence, orthographe.

L'aide-mémoire Git et l'aide-mémoire GitLab de Monsieur Nourrit pourront vous être utiles.

Import de la table « user » avec phpMyAdmin

Vous aurez besoin de votre propre table « user » pour travailler. Vous allez importer un script SQL qui crée cette table et y ajoute un utilisateur dont le login est « essai » et dont le mot de passe est « toto ». Cet utilisateur par défaut facilitera la tâche de votre intervenant de TP.

Travail à réaliser
  1. Connectez-vous sur phpMyAdmin
  2. Créez une nouvelle base de données « votre_login_authentication »
  3. Importez la table « user » MySQL (télécharger) dans votre nouvelle base MySQL à l'aide de phpMyAdmin
  4. Insérez ensuite quelques enregistrements dans la table
    Remarque importante

    Ne pas oublier d'appliquer la fonction SHA512 au mot de passe saisi.

    Vous pouvez utiliser plusieurs techniques (liste non exhaustive !) :

    • Produire des condensats SHA512 avec des outils en ligne ou utiliser la commande shell sha512sum :
      echo -n "votre_mot_de_passe" | sha512sum
      pour obtenir un mot de passe codé à insérer dans phpMyAdmin
    • Insérer vos enregistrements à l'aide de requêtes utilisant la fonction SHA2 de MySQL :
      INSERT INTO `user`(`id`, `lastName`, `firstName`, `login`, `sha512pass`, `phone`)
      VALUES (NULL, 'votre_nom', 'votre_prénom', 'votre_login', SHA2('votre_mdp', 512), 'votre_tel')
    • Insérer vos enregistrements à l'aide d'une requête paramétrée dans phpMyAdmin :
      INSERT INTO `user` (`id`, `lastName`, `firstName`, `login`, `sha512pass`, `phone`)
      VALUES (NULL, :lastName, :firstName, :login, SHA2(:pass, 512), :phone)
      Il suffit alors de cocher « Lier les paramètres » dans phpMyAdmin pour pouvoir saisir les paramètres : Lier les paramètres MySQL

Mise en place du projet

Pour interagir avec la base de données MySQL, vous utiliserez « MyPDO », le singleton de connexion basé sur « PDO ». Pour mémoire, le DSN « PDO » à utiliser est de la forme « mysql:host=mysql;dbname=votre_base;charset=utf8 ». Vous aurez également besoin de la classe « WebPage » et du trait « StringEscaper ».

Travail à réaliser
  1. Copiez le fichier « .mypdo.ini.dist » en « .mypdo.ini »
    Information

    Les fichiers « .dist » contiennent traditionnellement des éléments de configuration donnés à titre d'exemple dans la distribution d'un projet. La configuration fournie est factice mais elle pose les bases pour faciliter la mise en place du projet pour ceux qui l'installent.

  2. Éditez « .mypdo.ini » pour le faire correspondre à votre nouvelle base de données
    Remarque importante

    Le fichier « .mypdo.ini » contient votre mot de passe et ne doit donc pas être ajouté à l'index Git.

  3. Installez « PHP CS Fixer » en lançant composer install
  4. Configurez « PHP CS Fixer » dans PhpStorm

Gestion des données : classe « User »

Vous allez écrire, étape par étape, une classe « Entity\User » avec les contraintes de réalisation suivantes : Diagramme de la classe User

Travail à réaliser
  1. Créez à l'emplacement approprié dans la sous-arborescence de « src » la classe « Entity\User » dont les attributs privés correspondent aux champs « id », « lastName », « firstName », « login » et « phone » de la table « user » MySQL
    Remarque importante

    L'attribut « id » est de type entier.

    Pour des raisons de sécurité, le mot de passe « sha512pass » ne sera surtout pas utilisé comme attribut de l'objet, ni même sélectionné dans les requêtes.

  2. Utilisez votre IDE/éditeur de texte pour générer automatiquement les accesseurs
  3. Créez à l'emplacement approprié dans la sous-arborescence de « src » la classe « Entity\Exception\EntityNotFoundException »
  4. Écrivez la méthode de classe « findByCredentials(string $login, string $password): self » qui recherche dans la base de données l'utilisateur dont le login et le mot de passe sont passés en paramètre et retourne une instance de « User » ou lève une exception de type « EntityNotFoundException » si aucun enregistrement n'a été trouvé.
    Remarque importante

    Le mot de passe est fourni en clair alors que celui contenu dans la base de données est encodé en SHA512.

    Les données récupérées depuis la base de données par le biais de « PDOStatement » seront structurées sous forme d'instances de « UserAvatar ». Vous utiliserez donc la méthode « fetchObject() ».

    Utilisez « UneClasse::class » pour désigner le nom pleinement qualifié de la classe « UnEspaceDeNom\UnSousEspaceDeNom\UneClasse » lorsqu'elle est importée avec « use ».

  5. Vous n'écrirez aucun modificateur car ils sont inutiles pour les problématiques d'authentification et de sécurité mises en avant dans ce TP.
  6. Le profil de l'utilisateur sera géré par la classe « Html\UserProfile » : Diagramme de la classe UserProfile
    Information

    Notez que d'un point de vue conception pure, la méthode « escapeString() » du trait « StringEscaper » est une méthode de classe puisqu'aucune donnée d'instance n'est utilisée. Cependant, pour respecter les bonnes pratiques d'implémentation et faciliter l'utilisation de la méthode (notamment dans les chaînes de caractères), nous l'avons définie comme méthode d'instance.

    1. La classe utilise le trait « StringEscaper »
    2. La classe possède une unique propriété de type « User »
    3. Le constructeur recevra une instance de « User » et l'affectera à la propriété de l'instance courante
    4. La classe propose un accesseur sur la propriété de type « User »
    5. La méthode d'instance « toHtml() » permet de produire une mise en forme HTML très simple des noms, prénom, login (et identifiant entre crochets) et téléphone d'un utilisateur (en protégeant les valeurs à l'aide de la méthode « escapeString() » de « StringEscaper » Profil de l'utilisateur
      Remarque importante

      Il est primordial de protéger les valeurs issues de la base de données avant de les intégrer au contenu Web.

  7. Observez le code source PHP du programme de test « test-user.php » présent dans le répertoire de votre projet
    Information

    Notez l'utilisation de l'assistant « Html\Helper\Dumper » qui exploite la fonction « var_dump($une_variable) » mais propose de retourner le résultat plutôt que de l'afficher en tirant profit des fonctions de bufferisation de la sortie. Diagramme de la classe Dumper

  8. Vérifiez que votre classe « User » est pleinement fonctionnelle en comparant votre résultat du programme« test-user.php » à celui-ci :

Authentification : classe « UserAuthentication »

La logique d'authentification simple sera implémentée dans la classe « Authentication\UserAuthentication », principalement à travers ses méthodes « loginForm() » et « getUserFromAuth() ».

La logique chronologique des opérations sera la suivante : Authentification

Travail à réaliser

Vous allez écrire la classe « Authentication\UserAuthentication » : Diagramme de la classe UserAuthentication

  1. Créez la classe « Authentication\UserAuthentication » dans la sous-arborescence de « src »
  2. Dans la classe, définissez les constantes qui serviront à définir de façon fiable le nom des champs du formulaire de connexion
  3. Écrivez la méthode d'instance « loginForm(string $action, string $submitText = 'OK'): string » qui produit le formulaire de connexion
    Remarque importante

    Pensez à utiliser les constantes de classe « LOGIN_INPUT_NAME » et « PASSWORD_INPUT_NAME ».

  4. Placez « form.php » (télécharger) dans le répertoire des programmes Web
  5. Vérifiez qu'il permet de produire le formulaire d'authentification : Formulaire d'authentification
  6. Écrivez la classe « Authentication\Exception\AuthenticationException » héritant de « Exception »
  7. Écrivez la méthode d'instance « getUserFromAuth() » qui utilise la méthode « findByCredentials() » de la classe « User » en lui passant le login et le mot de passe issus du tableau « $_POST ». En cas de succès, l'objet « User » est retourné. En cas d'échec, une « AuthenticationException » est lancée.
    Remarque importante

    Pensez à utiliser les constantes de classe « LOGIN_INPUT_NAME » et « PASSWORD_INPUT_NAME ».

  8. Placez « auth.php » (télécharger) dans le répertoire des programmes Web
  9. Vérifiez qu'il permet de valider l'authenticité d'un utilisateur à partir du couple identifiant/mot de passe provenant du formulaire précédent
    Remarque importante

    Afin de déboguer efficacement, vérifiez le contenu de vos variables à l'aide de la méthode « dump($une_variable) » de l'assistant « Html\Helper\Dumper » ou la fonction « var_dump($une_variable) ».

Gestion de la session

Vous allez devoir mettre en place une session dans laquelle l'utilisateur sera mémorisé. Afin de faciliter cette gestion, vous reprendrez les classes développées lors du précédent TP. Diagramme de la classe Session

Travail à réaliser
  1. Recopiez la classe « Session » que vous avez écrite lors du précédent TP dans la sous-arborescence du répertoire « src »
  2. Recopiez la classe « SessionException » que vous avez écrite lors du précédent TP dans la sous-arborescence du répertoire « src »

Simulation d'un mode client/serveur connecté - Navigation

Les questions précédentes vous ont permis de vérifier l'authenticité de l'utilisateur grâce à son login et son mot de passe. Cette seule opération ne suffit cependant pas à simuler un mode connecté. Vous allez devoir mémoriser l'utilisateur après son authentification. Une consultation de cette donnée de session, associée à un client, permet de savoir si le client est « connecté ». La déconnexion consiste alors à détruire cette donnée de session.

Vous allez modifier la classe « UserAuthentication » en conséquence.

N'hésitez pas à consulter le manuel PHP consacré aux sessions.

La logique chronologique des opérations sera la suivante : Authentification et mémorisation dans la session

Mémorisation de l'objet « User » dans les données de session

La simulation du mode connecté nécessite des modifications de la classe « UserAuthentication » : Diagramme de la classe UserAuthentication

Information

Le diagramme UML de la classe « UserAuthentication » présente la précédente version des propriétés et méthodes en gris clair et les nouveaux éléments en blanc.

Travail à réaliser
  1. Définissez les constantes privées suivantes dans la classe « UserAuthentication » qui permettront de préserver des valeurs de clés fiables dans le tableau des données de session :
    • SESSION_KEY = '__UserAuthentication__'
    • SESSION_USER_KEY = 'user'
    Information

    Les données de session pouvant être utilisées par plusieurs composants logiciels, vous stockerez les informations relatives à l'authentification dans un sous-tableau de « $_SESSION » accessible par la clé « '__UserAuthentication__' ». L'utilisateur sera stocké à la clé « 'user' » du sous-tableau. Ces deux valeurs de clés seront obtenues grâce aux constantes de classe.

  2. Ajoutez la propriété « $user » de type « User » à la classe « UserAuthentication »
  3. Faites en sorte que la propriété « $user » soit nullable et initialisez-la à « null »
  4. Écrivez la méthode « setUser(User $user) » qui affecte l'utilisateur passé en paramètre à la propriété « $user » de l'instance courante et qui mémorise cet objet « User » dans les données de session
    Remarque importante

    Pensez à démarrer la session.

  5. Modifiez la méthode « getUserFromAuth() » de la classe « UserAuthentication » pour qu'elle fasse usage de la méthode « setUser() ».
  6. Écrivez une méthode publique d'instance « isUserConnected() » de la classe « UserAuthentication » qui retourne « true » si la donnée de session contient une instance de « User »
  7. Placez le script « connected.php » (télécharger) dans le répertoire des programmes Web
  8. Pour en interdire l'accès aux visiteurs non connectés, vous effectuerez une redirection HTTP vers le formulaire de connexion si l'utilisateur n'est pas connecté (voir « header() » en considérant l'ensemble de ses paramètres)
  9. Vérifiez que « connected.php » redirige bien sur « form.php » lorsque vous n'êtes pas connecté
  10. Vérifiez que le contenu de « connected.php » est visible lorsque vous êtes connecté
    Information

    Vous pouvez facilement vous déconnecter en effaçant le cookie de session « PHPSESSID » à l'aide de la barre développement de votre navigateur (onglet « Application » pour Chrome, onglet « Stockage » pour Firefox).

Déconnexion de l'utilisateur connecté

La déconnexion nécessite de nouvelles modifications de la classe « UserAuthentication » : Diagramme de la classe UserAuthentication

Travail à réaliser
  1. Ajoutez à la classe « UserAuthentication » la constante « LOGOUT_INPUT_NAME » qui servira à porter le nom du bouton de déconnexion
  2. Ajoutez à votre classe « UserAuthentication » la méthode « logoutForm(string $action, string $text) » qui retourne le code HTML correspondant à un formulaire de déconnexion utilisant la méthode « POST »
    Remarque importante

    Pensez à utiliser la nouvelle constante de classe.

  3. Écrivez une méthode publique d'instance « logoutIfRequested() » de la classe « UserAuthentification » qui efface la donnée de session associée à l'utilisateur si une valeur « 'logout' » de formulaire est reçue
    Remarque importante

    Pensez à utiliser la nouvelle constante de classe et à démarrer la session.

  4. Modifiez le script « form.php » afin qu'il propose une solution pour déconnecter l'utilisateur s'il l'est et propose le formulaire de connexion si aucun utilisateur n'est connecté.

Restauration de l'objet depuis les données de session

Le point précédent permet de simuler un mode client/serveur connecté. Il ne permet cependant pas d'obtenir un fonctionnement transparent pour récupérer l'objet « User » stocké dans les données de session. Les modifications à suivre vont donc porter sur la classe « UserAuthentication » : Diagramme de la classe UserAuthentication

La logique chronologique des opérations sera la suivante : Authentification et mémorisation dans la session puis restauration

Travail à réaliser
  1. Implémentez la méthode d'instance protégée « getUserFromSession() » qui retourne l'objet « User » correspondant à la variable de session précédemment utilisée, à condition que celle-ci contienne bien un objet « User ». Elle lève une exception de type « NotLoggedInException » dans le cas contraire.
  2. Écrivez la classe « NotLoggedInException » qui hérite de « Exception »
  3. Ajoutez un constructeur public à la classe « UserAuthentication ».
  4. Faites appel à la méthode « getUserFromSession() » dans le constructeur afin de restaurer la propriété « $user » de la classe si cela est possible (capture des exceptions de type « NotLoggedInException » sinon)
  5. Écrivez une méthode « getUser() » qui retourne la propriété « $user » de l'instance courante si elle est définie et lève une « NotLoggedInException » sinon
  6. Modifiez le programme « form.php » pour qu'il affiche l'utilisateur connecté s'il en existe un et propose le formulaire de connexion si aucun utilisateur n'est enregistré dans les données de session en supprimant l'utilisation de la méthode « isUserConnected() »
  7. Vous devez constater que la déconnexion nécessite deux demandes pour être effective
  8. Dans la méthode « logoutIfRequested() » affectez « null » à la propriété « $user » pour que votre classe considère qu'aucun utilisateur n'est connecté (il avait été récupéré depuis les données de session avant qu'elles ne soient détruites)
  9. Créez un script « authenticated.php » sur la base de « connected.php » qui effectuera une redirection pour les visiteurs non connectés mais affichera le prénom de l'utilisateur authentifié.

Sujet complémentaire

Si vous avez traité l'ensemble des questions du sujet, vous pouvez poursuivre avec le sujet complémentaire.