Dans ce tutoriel, nous apprendrons à gérer une session utilisateur en PHP ainsi que toute la sécurité qui va avec. Connexion, déconnexion, token de sécurité, vérification, tout y est.

Cependant, nous n’utiliserons pas de base de données, mais un fichier JSON. Cette méthode n’est pas optimisée ni sécurisée. Mais pas de panique, nous allons décomposer le code en fonctions simples à modifier pour que vous puissiez la connecter à une base de données. Vous allez comprendre.

À noter que dans le fichier JSON, les utilisateurs seront stockés sous forme d’objets avec en clé leur adresse e-mail et en valeur un tableau associatif avec leur mot de passe (encrypté) et leur token.

Ce tutoriel sera autant théorique que pratique. Vous trouverez tout à la fin le fichier complet avec l’intégralité du code. Have fun !

Déposez gratuitement votre projet sur Codeur.com, recevez une quinzaine de devis et sélectionnez le développeur web idéal.

Trouver un développeur web

Générer les sauvegardes des utilisateurs

Pour  commencer, nous allons créer un dossier data dans lequel on insère du fichier users.json. À l’intérieur de ce dernier, nous ajouterons un objet vide :

{}

Et maintenant le PHP. Nous allons commencer par une première fonction dont le but sera tout simplement de retourner le chemin absolu du fichier json.

  • Le chemin absolu est l’url du fichier à partir de la racine du disque dur. Durant la rédaction du tutoriel, la fonction ci-dessous m’a renvoyé c:/wamp64/www/tuto-codeur/data/user.json. Attention de ne pas confondre avec le chemin relatif qui lui est le chemin vers un fichier depuis le fichier où est exécuté le code.
  • La constante __DIR__ contient le chemin absolu du fichier depuis lequel est exécuté le code. Attention, il ne prend pas en compte les inclusions.
/**
 * Retourne le chemin absolu vers le fichier qui contient les utilisateurs
 * @return string Chemin absolu du fichier
*/
function getUsersFilePath() {
  return __DIR__ . "/data/users.json";
}

Lister tous les utilisateurs

Le but de la prochaine fonction sera de récupérer la liste de tous les utilisateurs. Pour cela, rien de plus simple, il nous suffit de lire le contenu du fichier JSON en trois courtes étapes :

    1. Récupérer le lien du fichier. Il est retourné par la fonction getUsersFilePath.
    2. Ouvrir et lire le contenu du fichier. Le plus simple reste d’utiliser la fonction file_get_contents.
    3. Transformer le JSON en array avec la fonction json_decode. Attention de ne pas oublier le second paramètre true, sinon vous allez vous retrouver avec un objet.
/**
 * Récupère la liste complète de tous les utilisateurs
 * @return array Liste des utilisateurs
*/
function getUsers() {
  try {
    // Récupération du contenu du fichier
    $content = file_get_contents( getUsersFilePath() );
    // Conversion du JSON en Array
    return json_decode( $content, true );
  }
  catch( Exception $e ) {
    die( $e->getMessage() );
  }
}

Charger un utilisateur

Maintenant, on veut pouvoir récupérer un seul utilisateur. C’est très simple. La fonction getUsers que nous venons de créer nous retourne la liste de tous les utilisateurs sous forme de tableau associatif dont les clés sont des adresses e-mail des utilisateurs. Il nous suffit donc de l’appeler et de chercher l’utilisateur par son email. Pour vérifier que la présence d’une clé dans un tableau associatif, nous pouvons utiliser la fonction array_key_exists.

/**
 * Récupère un seul utilisateur s'il existe
 * @param string $email Adresse email de l'utilisateur
 * @return array|bool l'utilisateur s'il existe, sinon false
 */
function getUser( $email ) {
    // Récupération de la liste de tous les utilisateurs
    $users = getUsers();

    // Si l'adresse e-mail est une clé de la liste des utilisateurs ...
    return array_key_exists($email, $users)
        ? $users[$email] // alors: retourne la valeur qui correspond
        : false;         // sinon: retourne faux
}

Nous sommes maintenant capables de récupérer tous les utilisateurs ou un seul que l’on recherche par rapport à son adresse e-mail.

Mettre à jour le fichier

Vous le savez, nos utilisateurs sont stockés dans un fichier. Pour pouvoir en modifier ou en ajouter un, nous faudra donc lire l’intégralité du fichier avec la fonction getUsers, puis effectuer les modifications. Mais en suite, il faudra pouvoir mettre à jour le fichier qui contient les utilisateurs. Nous allons donc créer une nouvelle fonction qui va :

  1. Prendre la liste des utilisateurs qu’on lui envoie en paramètres.
  2. Convertir cette liste en JSON avec la méthode json_decode. Peu optimisé, mais plus lisible : il est possible d’obtenir un JSON propre et indenté en ajouter en second paramètre JSON_PRETTY_PRINT.
  3. Mettre à jour le fichier. Le chemin de ce dernier est récupéré avec notre fonction getUsersFilePath. Pour la modification du contenu, nous pouvons utiliser la fonction file_put_contents.
/**
 * Met à jour la liste des utilisateurs dans le fichier
 * @param array $users Liste des utilisateurs
 */
function saveUsers( $users ) {
    try {
        // Conversion de la liste des utilisateurs en JSON indenté
        $content = json_encode( $users, JSON_PRETTY_PRINT );
        // Remplacement du contenu du fichier.
        file_put_contents( getUsersFilePath(), $content );
    }
    catch( Exception $e ) {
        die( $e->getMessage() );
    }
}

Ajouter un utilisateur

Maintenant que nous savons modifier la liste des utilisateurs, il reste une dernière chose : en ajouter un nouveau ! Et pour cela encore, nous allons créer une nouvelle fonction. Cette dernier va :

  1. Récupérer l’adresse et le mot de passe de l’utilisateur via les paramètres ;
  2. Crypter le mot de passe avec la fonction hashPassword que nous verrons juste après ;
  3. Générer un nouveau token utilisateur avec la future fonction generateToken;
  4. Récupérer la liste des utilisateurs (fonction getUsers) ;
  5. Insérer l’utilisateur dans le tableau associatif ;
  6. Sauvegarder la liste (fonction saveUsers).
)/**
 * Ajoute utilisateur dans le fichier
 * @param string $email Adresse email de l'utilisateur
 * @param string $password Mot de passe non hashé
 */
function addUser( $email, $password ) {
    // Récupération de la liste de tous les utilisateurs
    $users = getUsers();
    // Ajout du nouvel utilisateur
    $users[$email] = [
        'password' => hashPassword( $password ),
        'token'    => generateToken()
    ];
    // Sauvegarde de la liste des utilisateurs
    saveUsers( $users );
}

 

Gérer la sécurité

Nous allons maintenant entrer dans la thématique de la sécurité. Et vous allez voir que nous n’allons rien faire au hasard. Pour commencer, voyons les deux fonctions que nous appelons depuis addUser.

Mot de passe

Sauvegarder un mot de passe, c’est mal. Toutes les personnes qui ont accès à la base de données pourront voir le mot de passe de tous les utilisateurs. Du coup, si votre base de données est piratée (ce qui est encore plus facile avec un fichier), toutes les données seront accessibles au hackeur. Nous allons donc crypter, ou « hasher » le mot de passe. Il existe plusieurs façons d’encrypter un mot de passe. Ici, nous allons tout simplement utiliser la fonction sha1 déjà installée sur PHP.

/**
 * Hash un mot de passe
 * @param string $password Mot de passe non hashé
 * @return stirng Mot de passe hashé
 */
function hashPassword( $password ) {
    // Retour du mot de pass hashé
    return sha1( $password );
}

Token de sécurité

Pour le token de sécurité, nous allons tout simplement générer une chaîne de caractère aléatoire.

Le token sera régénéré à chaque fois que l’utilisateur se connecte puis stocké dans la session de l’utilisateur. À chaque requête, nous vérifions que le token dans la session est le même que dans la base de données. Si ce n’est pas le cas, cela veut dire qu’une autre personne s’est connecté avec le même compte.

/**
 * Génère un nouveau token
 * @param string $length Taille du token souhaité. Par défaut : 40
 * @return string Token généré
 */
function generateToken( $length = 40 ) {
    $characters       = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_!?./$';
    $charactersLength = strlen( $characters );
    $token            = '';
    for( $i = 0; $i < $length; $i++ ) {
        $token .= $characters[rand(0, $charactersLength - 1)];
    }
    return $token;
}

 

Gestion des utilisateurs

Ok, tout est en place : la lecture et écriture dans le fichier (que vous pouvez remplacer par une base de données) et le point sur la sécurité. Attaquons le dernier point : l’utilisateur !

Enregistrer un nouvel utilisateur

Quand utilisateur a rempli son formulaire d’inscription et que tous les champs sont valides, nous allons pouvoir l’ajouter dans le fichier. Mais avant cela, il faudra vérifier qu’il n’y soit pas. Et si tout est bon, nous appellerons la fonction addUser pour l’ajouter dans le fichier.

/**
 * Enregistre un nouvel utilisateur
 * @param string $email Adresse e-mail de l'utilisateur
 * @param string $password Mot de passe non hashé
 */
function register( $email, $password ) {
    // Récupération de l'utilisateur demandé
    $user = getUser( $email );
    // Si l'utilisateur existe déjà, on arrête tout.
    if( $user ) {
        die( "L'utilisateur {$email} est déjà enregistré." );
    }

    // Enregistrement du nouvel utilisateur
    addUser( $email, $password );
}

Vous pouvez tester à la fin de votre fichier.

/*
 * ZONE DE TESTS
 */
register('[email protected]', 'motDePasse');

Voici ce qui apparaît dans le fichier JSON après le premier refresh.

{
    "[email protected]": {
        "password": "1829bca2a2e6210239ce329dabf70722a71d8873",
        "token": "tNM-kz4q_y2DNBMns1TAJ1jDmKjvb749apYGgN-d"
    }
}

Au second refresh, vous devriez avoir une erreur affichée à l’écran.

Se connecter

Une fonction assez complète, celle de la connexion.

Pour commencer, nous allons charger la liste des utilisateurs et vérifier qu’il soit bien présent. Nous n’allons pas utiliser la fonction getUser. Si elle retourne un false, c’est que l’utilisateur n’a pas été trouvé ; nous allons donc afficher une erreur et tout stopper.

/**
 * Tente de connecter un utilisateur. Affecte les sessions.
 * @param string $email Adresse e-mail de l'utilisateur
 * @param string $password Mot de passe non hashé
 */
function login( $email, $password ) {
    // Récupération de la l'utilisateur
    $user = getUser( $email );

    // Si l'utilisateur n'a pas pu être récupéré.
    if( ! array_key_exists($email, $users) ) {
        die( "L'utilisateur {$email} n'est pas enregistré." );
    }
    // ...

Après cela, nous allons comparer le mot de passe dans le fichier (ou dans la base de données) avec le mot de passe saisi, que nous n’oublieront pas de crypter (ou « hasher »). Si les deux ne correspondent pas, nous afficheront une nouvelle erreur.

    // ...
    // Si le mot de passe (hashé) ne correspond pas, on arrête tout.
    if( $users[$email]['password'] !== hashPassword($password) ) {
        die( "L'utilisateur {$email} n'est pas enregistré." );
    }
    // ...

Si on arrive à la suite, c’est que l’email et le mot de passe sont corrects et bien associés. De là, nous allons pouvoir générer le nouveau token de sécurité.

    // ...
    // Génération d'un nouveau token de sécurité.
    $token = generateToken();
    // ...

Ce nouveau token sera appliqué sur l’utilisateur que nous modifieront dans le fichier.

    // ...
    // Enregistrement du nouveau token et sauvegarde des utilisateurs
    $users[$email]['token'] = $token;
    saveUsers( $users );
    // ...

Et pour terminer, nous allons pouvoir enregistrer les données dans la session de l’utilisateur. Pas de panique pour la variable $_SESSION, nous allons voir cela dans la prochaine partie.

    // ...
    // Enregistrement des données dans la session de l'utilisateur
    $_SESSION['user_email'] = $email;
    $_SESSION['user_token'] = $token;
}

Voici donc la fonction complète.

/**
 * Tente de connecter un utilisateur. Affecte les sessions.
 * @param string $email Adresse e-mail de l'utilisateur
 * @param string $password Mot de passe non hashé
 */
function login( $email, $password ) {
    // Récupération de la l'utilisateur
    $user = getUser( $email );

    // Si l'utilisateur n'a pas pu être récupéré.
    if( ! array_key_exists($email, $users) ) {
        die( "L'utilisateur {$email} n'est pas enregistré." );
    }

    // Si le mot de passe (hashé) ne correspond pas, on arrête tout.
    if( $users[$email]['password'] !== hashPassword($password) ) {
        die( "L'utilisateur {$email} n'est pas enregistré." );
    }

    // Génération d'un nouveau token de sécurité.
    $token = generateToken();

    // Enregistrement du nouveau token et sauvegarde des utilisateurs
    $users[$email]['token'] = $token;
    saveUsers( $users );

    // Enregistrement des données dans la session de l'utilisateur
    $_SESSION['user_email'] = $email;
    $_SESSION['user_token'] = $token;
}

Vous pouvez maintenant de teste l’utilisateur.

/*
 * ZONE DE TESTS
 */
// register('[email protected]', 'motDePasse');
login('[email protected]', 'motDePasse');

Vous devriez constater que, dans le fichier JSON, la token de l’utilisateur a changé.

{
    "[email protected]": {
        "password": "1829bca2a2e6210239ce329dabf70722a71d8873",
        "token": "l0NtwgB7\/_eIm$8DFxwyIiioZUFyZH16Y?khNz\/R"
    }
}

Vous pouvez également utiliser la fonction var_dump pour voir le contenu de variable $_SESSION.

/*
 * ZONE DE TESTS
 */
// register('[email protected]', 'motDePasse');
// login('[email protected]', 'motDePasse');
var_dump( $_SESSION );
var_dump( getUsers() );

Qu’est-ce que $_SESSION ?

Petit aparté dans le code pour faire un point sur la variable super globale $_SESSION. Pour plus d’informations sur les variables super globales, veuillez consulter notre précédent chapitre sur PHP : Variable globale PHP&nbsp;: à quoi ça sert&nbsp;?

Cette variable servira donc à enregistrer des données utilisateur le temps de sa session de visite. Théoriquement, une fois qu’il quitte le site, cette variable est automatiquement vidée. Après, cela dépend du navigateur et des configurations de l’utilisateur.

Contrairement aux cookies que nous verront dans le prochain tutoriel dédié à PHP, la session ne demande pas de manipulation particulière. Modifier la variable (qui est un array) comme une autre variable suffit à mettre à jour la session.

Dans la fonction login, quand nous effectuons $_SESSION['user_email'] = $email; et $_SESSION['user_token'] = $token;, nous indiquons que l’utilisateur aura donc comme données de sessions user_email et user_token qui sont respectivement son adresse e-mail et son token de sécurité.

Vérifier la connexion

À présent, petit point  sur la sécurité. À chaque requête, nous allons appeler la suivante fonction isLogged, dont le but est de vérifier que l’utilisateur connecté soit toujours valide. Pour rappel, à chaque fois qu’on se connecte, le token chance dans le fichier et le même est copié dans la session de l’utilisateur. Si une seconde personne se connecte, le token sera mis à jour et la première personne ne sera donc plus valide. Cela permet de détecter plus facilement un hacker, ou de tout simplement bloquer le hackeur en se reconnectant et en modifiant rapidement son mot de passe.

/**
 * Vérifie que l'utilisateur soit connecté et que son token est valide
 * @return bool Indique si l'utilisateur et connecté et valide
 */
function isLogged() {
    // Si la session contient "user_email" et "user_token"
    if( isset($_SESSION['user_email']) && isset($_SESSION['user_token']) ) {
        // Récupération de l'utilisateur dans la liste
        $user = getUser( $_SESSION['user_email'] );
        
        // Si un utilisateur a bien été récupéré
        if( $user ) {
            // Si le token de la session correspond au token dans le fichier
            if( $_SESSION['user_token'] === $user['token'] ) {
                // Tout est bon
                return true;
            }
        }
    }

    // Une erreur a empêché d'arriver au "return true"
    return false;
}

Petit test ? Là tout devrait fonctionner.

/*
 * ZONE DE TESTS
 */
// register('[email protected]', 'motDePasse');
// login('[email protected]', 'motDePasse');
var_dump( $_SESSION );
var_dump( isLogged() );
//var_dump( getUsers() );

Un second test où on change à la fin le token dans la session pour simuler un changement de dernier et aussi pour qu’il ne corresponde plus à celui enregistré dans le fichier. Vous devriez avec un message d’erreur qui s’affiche.

/*
 * ZONE DE TESTS
 */
// register('[email protected]', 'motDePasse');
// login('[email protected]', 'motDePasse');
$_SESSION['user_token'] = 'test';
var_dump( $_SESSION );
var_dump( isLogged() );
//var_dump( getUsers() );

Déconnecter

Pour se connecter, il suffit tout simplement de vider la session. La méthode la plus simple et la plus efficace est d’utiliser la fonction array_destroy, qui, comme son nom l’indique, va tout simplement détruire la session.

/**
 * Déconnecte l'utilisateur (affecte la session)
 */
function logout() {
    session_destroy();
}

Et voilà si on test.

/*
 * ZONE DE TESTS
 */
// register('[email protected]', 'motDePasse');
// login('[email protected]', 'motDePasse');
// $_SESSION['user_token'] = 'test';
logout();
var_dump( $_SESSION );
var_dump( isLogged() );
//var_dump( getUsers() );

 

Notre astuce pour gérer des sessions en PHP

Ce tutoriel a été un peu long et surtout complet. Vous êtes à présent capable de gérer une session utilisateur en PHP. Vous connaissez des fondamentaux en termes de sécurité, et vous savez utiliser la variable super globale $_SESSION.

Cependant, nous recommandons très fortement de ne plus utiliser le fichier mais de passer par une base de données. Il vous faudra rajouter une fonction pour s’y connecter, et mettre à jour les fonctions getUsers, getUser, et saveUsers. Oui, c’est tout. C’est l’avantage d’un code bien découpé en plusieurs fonctions.

En cas de problème avec le système de connexion ou l’ajout de base de données, vous pouvez faire appel à un développeur PHP freelance spécialisé en déposant gratuitement une annonce sur Codeur.com.

 

 

Et avant de se quitter, voici comme promis le code complet.

<?php session_start();

/**
 * Retourne le chemin absolu vers le fichier qui contient les utilisateurs
 * @return string Chemin absolu du fichier
 */
function getUsersFilePath() {
  return __DIR__ . "/data/users.json";
}

/**
 * Récupère la liste complète de tous les utilisateurs
 * @return array Liste des utilisateurs
 */
function getUsers() {
  try {
    // Récupération du contenu du fichier
    $content = file_get_contents( getUsersFilePath() );
    // Convertion du JSON en Array
    return json_decode( $content, true );
  }
  catch( Exception $e ) {
    die( $e->getMessage() );
  }
}

/**
 * Recupère un seul utilisateur s'il existe
 * @param string $email Adresse email de l'utilisateur
 * @return array|bool l'utilisateur s'il existe, sinon false
 */
function getUser( $email ) {
    // Récupéartion de la liste de tous les utilisateurs
    $users = getUsers();

    // Si l'adresse e-mail est une clé de la liste des utilisateurs ...
    return array_key_exists($email, $users)
        ? $users[$email] // alors: retourne la valeur qui correspond
        : false;         // sinon: retourne faux
}

/**
 * Ajoute utilisateur dans le fichier
 * @param string $email Adresse email de l'utilisateur
 * @param string $password Mot de passe non hashé
 */
function addUser( $email, $password ) {
    // Récupération de la liste de tous les utilisateurs
    $users = getUsers();
    // Ajout du nouvel utilisateur
    $users[$email] = [
        'password' => hashPassword( $password ),
        'token'    => generateToken()
    ];
    // Sauvegarde de la liste des utilisateurs
    saveUsers( $users );
}

/**
 * Met à jour la liste des utilisateurs dans le fichier
 * @param array $users Liste des utilisateurs
 */
function saveUsers( $users ) {
    try {
        // Conversion de la liste des utilisateurs en JSON indenté
        $content = json_encode( $users, JSON_PRETTY_PRINT );
        // Remplacement du contenu du fichier.
        file_put_contents( getUsersFilePath(), $content );
    }
    catch( Exception $e ) {
        die( $e->getMessage() );
    }
}

/**
 * Hash un mot de passe
 * @param string $password Mot de passe non hashé
 * @return stirng Mot de passe hashé
 */
function hashPassword( $password ) {
    // Retour du mot de pass hashé
    return sha1( $password );
}

/**
 * Génère un nouveau token
 * @param string $length Taille du token souhaité. Par défaut : 40
 * @return string Token généré
 */
function generateToken( $length = 40 ) {
    $characters       = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_!?./$';
    $charactersLength = strlen( $characters );
    $token            = '';
    for( $i = 0; $i < $length; $i++ ) {
        $token .= $characters[rand(0, $charactersLength - 1)];
    }
    return $token;
}

/**
 * Enregistre un nouvel utilisateur
 * @param string $email Adresse e-mail de l'utilisateur
 * @param string $password Mot de passe non hashé
 */
function register( $email, $password ) {
    // Récupération de l'utilisateur demandé
    $user = getUser( $email );
    // Si l'utilisateur existe déjà, on arrête tout.
    if( $user ) {
        die( "L'utilisateur {$email} est déjà enregistré." );
    }

    // Enregistrement du nouvel utilisateur
    addUser( $email, $password );
}

/**
 * Tente de connecter un utilisateur. Affecte les sessions.
 * @param string $email Adresse e-mail de l'utilisateur
 * @param string $password Mot de passe non hashé
 */
function login( $email, $password ) {
    // Récupération de l'utilisateur
    $user = getUser( $email );

    // Si l'adresse e-mail n'a pas été trouvé.
    if( ! $user ) {
        die( "L'utilisateur {$email} n'est pas enregistré." );
    }

    // Si le de passe (hashé) ne correspond pas, on arrête tout.
    if( $users[$email]['password'] !== hashPassword($password) ) {
        die( "L'utilisateur {$email} n'est pas enregistré." );
    }

    // Génération d'un nouveau token de sécurité.
    $token = generateToken();

    // Enregistrement du nouveau token et sauvegarde des utilisateurs
    $users[$email]['token'] = $token;
    saveUsers( $users );

    // Enregistrement des données dans la session de l'utilisateur
    $_SESSION['user_email'] = $email;
    $_SESSION['user_token'] = $token;
}

/**
 * Vérifie que l'utilisateur soit connecté et que son token est valide
 * @return bool Indique si l'utilisateur et connecté et valide
 */
function isLogged() {
    // Si la session contient "user_email" et "user_token"
    if( isset($_SESSION['user_email']) && isset($_SESSION['user_token']) ) {
        // Récupération de l'utilisateur dans la liste
        $user = getUser( $_SESSION['user_email'] );
        
        // Si un utilisateur a bien été récupéré
        if( $user ) {
            // Si le token de la session correspond au token dans le fichier
            if( $_SESSION['user_token'] === $user['token'] ) {
                // Tout est bon
                return true;
            }
        }
    }

    // Une erreur a empêché d'arriver au "return true"
    return false;
}

/**
 * Déconnecte l'utilisateur (affecte la session)
 */
function logout() {
    session_destroy();
}

/*
 * ZONE DE TESTS
 */
// register('[email protected]', 'motDePasse');
// login('[email protected]', 'motDePasse');
// $_SESSION['user_token'] = 'test';
// logout();
// var_dump( $_SESSION );
// var_dump( isLogged() );
// var_dump( getUsers() );