VA
Comment lire ce livre ? Qui sommes nous ?
I. PAGES WEB
II. SERVEUR WEB
III. BASES DE DONNEES
IV. INTERACTION FRONT-BACK
Annexe I. ENSEIGNEMENT NSI
Annexe II. LES FRAMEWORKS

Chapitre 10 - Session et compte utilisateur

Certaines applications web doivent garder un état et maintenir une session avec leurs utilisateurs afin de leur fournir un service spécifique. C'est le cas par exemple des sites de e-commerce qui doivent garder en mémoire le contenu du panier d'un utilisateur entre deux requêtes successives, que l'utilisateur soit connecté avec son compte ou pas.

Nous présentons dans ce chapitre le principe fondamental qui permet de maintenir ce concept de session entre un serveur web et un utilisateur. Nous décrivons ensuite comment mettre en oeuvre ce principe à l'aide des cookies. Enfin, nous terminons en expliquant une façon de gérer des comptes utilisateurs avec une persistance dans une base de données et expliquons comment exploiter les sessions dans ce contexte.

Le concept de session

HTTP est un protocole sans état car il ne définit aucun moyen natif pour garder en mémoire un état entre l'envoi de requêtes. En utilisant HTTP nativement, c'est à dire sans ajouter d'information dans les requêtes et dans les réponses, il n'est par exemple pas possible de savoir si deux requêtes ont été envoyées par le même utilisateur ou par deux utilisateurs différents.

Pour faire face à cette spécificité du protocole HTTP, les serveurs web mettent en place un mécanisme propre qui leur permet de créer un état entre les requêtes, c'est ce qu'on appelle le concept de session. Une session est créée et maintenue par un serveur web et permet de reconnaître les requêtes envoyées par un utilisateur parmi celles envoyées par d'autres utilisateurs.

La création d'une session est faite par le serveur web lorsqu'il reçoit une requête d'un utilisateur qu'il ne connait pas. Une fois créée, la session est stockée sur le serveur web en mémoire vive ou dans une base de données. Une session est propre à un utilisateur. Elle contient toutes les informations utiles à la gestion des requêtes de l'utilisateur.

Pour que chaque session maintienne un lien avec un utilisateur, le serveur web attribue un identifiant unique à chaque session, envoie cet identifiant unique dans la réponse qu'il renvoie à l'utilisateur, et lui demande de rappeler cet identifiant à chaque fois qu'il fera une nouvelle requête. Ainsi fait, le serveur web pourra reconnaitre les requêtes qui lui sont envoyées, retrouver les sessions à laquelle elles appartiennent, et obtenir toutes les informations contenues dans ces sessions.

La Figure 10.1 illustre le principe de fonctionnement d'une session. On voit que la session est créée par le serveur web et que l'identifiant de la session est envoyé dans toutes les requêtes suivantes. C'est cet identifiant qui permet au serveur web de retrouver la session correspondante et ainsi de retrouver toutes les données nécessaires à la gestion des requêtes. Même si on considère que la session s'opère entre un serveur web et un utilisateur, il faut bien comprendre que c'est le navigateur web qui envoie les requêtes au serveur web, la session effectue donc un lien entre un navigateur web et un serveur web.

Alt Text

Figure 10.1 : Mise en place d'une session entre un navigateur web et un serveur web.

Définition
Une session est un mécanisme mis en place par un serveur web pour reconnaître les requêtes qui sont envoyées par un même utilisateur au travers de son navigateur web. Une session est identifiée par un identifiant unique. Cet identifiant doit être transmis à chaque nouvelle requête envoyée par le navigateur web afin que le serveur puisse reconnaître la session à laquelle la requête appartient. Enfin, une session contient des données stockées sur le serveur web. Ces données sont propres à l'utilisateur et permettent au serveur web de gérer les requêtes de l'utilisateur.

Les Cookies

Pour mettre en place le mécanisme de session il faut que le serveur web transmette l'identifiant de la session au navigateur web et qu'il demande à celui-ci de le retransmettre à chaque nouvelle requête. Sans cet identifiant ajouté à chaque requête il n'est pas possible pour le serveur web de retrouver la session correspondante.

Il existe plusieurs solutions pour répondre à ce besoin de transmission de l'identifiant de la session. La solution la plus commune (et la plus ancienne) consiste à utiliser le mécanisme de Cookie HTTP qui a d'ailleurs été défini à cet effet.

Définition
Un cookie est un fichier texte qui est enregistré par le navigateur web à la demande du serveur web et dont le contenu sera envoyé par le navigateur web à chaque nouvelle requête vers ce serveur web.

La demande de création d'un cookie se fait par le serveur web. Il suffit de rajouter un en-tête Set-Cookie dans la réponse envoyée par le serveur web. Le contenu de l'en-tête Set-Cookie est le contenu du cookie qui sera enregistré par le navigateur web et retransmis à chaque requête. Par exemple, si la réponse à une requête HTTP contient l'en-tête Set-Cookie: session-id=1234567890 alors le navigateur qui recevra cette réponse créera un cookie qui contiendra de l'information avec session-id comme clé et 1234567890 comme valeur. Lorsque ce navigateur renverra des requêtes vers le serveur web, il ajoutera le contenu de ce cookie dans l'en-tête de la requête (Cookie: session-id=1234567890).

La mise en place d'une session se fait donc assez naturellement avec les cookies. Si le serveur web reçoit une requête sans cookie, il crée une nouvelle session et, dans la réponse, demande au navigateur web d'enregistrer un nouveau cookie contenant l'identifiant de la session. Ainsi fait, chaque nouvelle requête envoyée par le navigateur web contiendra le cookie et le serveur web pourra alors retrouver la session correspondante.

Le Code 10.1 illustre la mise en place par le serveur d'une session avec les cookies. L'objet est ici de mettre en place des sessions qui comptent le nombre de visites effectuées par un utilisateur. Les sessions ont un identifiant unique qui démarre à 0 et qui s'incrémente à chaque nouvelle session créée (variable lastSessionId). Les sessions sont stockées dans un tableau (variable sessions). Lorsque le serveur reçoit une requête, il vérifie si le cookie contenant l'identifiant de la session est présent dans la requête. Cette vérification se fait en regardant les en-têtes de la requête (req.headers['cookie']) puis en cherchant la clé session-id. Si la requête ne contient pas de cookie, ou si le cookie ne contient pas la clé session-id, alors le serveur web crée une nouvelle session et envoie un cookie contenant l'identifiant de la session dans la réponse. Si au contraire, la requête contient le cookie avec la clé session-id, alors le serveur web récupère l'identifiant de la session et incrémente le nombre de visites de la session correspondante.

const http = require("http");
const host = 'localhost';
const port = 8080;
const server = http.createServer();

let lastSessionId = 0;
let sessions = [];

server.on("request", (req, res) => {
    if (req.url === '/') {
        let hasCookieWithSessionId = false;
        let nbVisites = 1;
        if (req.headers['cookie'] !== undefined) {
            let sessionIdInCookie = req.headers['cookie'].split(';').find(item => item.trim().startsWith('session-id'));
            if (sessionIdInCookie !== undefined) {
                let sessionId = parseInt(sessionIdInCookie.split('=')[1]);
                if (sessions[sessionId]) {
                    hasCookieWithSessionId = true;
                    nbVisites = sessions[sessionId].nbVisites++;
                }
            }
        }

        if (!hasCookieWithSessionId) {
            lastSessionId++;
            res.setHeader('Set-Cookie', `session-id=${lastSessionId}`);
            sessions[lastSessionId] = {
                'nbVisites': nbVisites
            }
        } 
        let html = '<html><body><h1>Page</h1><p>Nombre de visites: ' + nbVisites + '</p></body></html>';
        res.end(html);
    }
});

server.listen(port, host, () => {
    console.log(`Server running at http://${host}:${port}/`);
});

Code 10.1 : Exploitation des cookies pour mettre en place le mécanismes de session.

L'exemple du code 10.1 montre la différence entre la session utilisateur qui est stockée ici en mémoire vive dans le tableau sessions et les cookies qui ne contiennent que l'identifiant de la session. Il est en effet déconseillé de transmettre l'intégralité des sessions dans les cookies car cela peut représenter un volume de données important et cela peut poser des problèmes de sécurité. Il vaut mieux stocker les données de la session sur le serveur web et ne transmettre que l'identifiant de la session dans les cookies.

Compte utilisateur

Le concept de session appelle à la mise en place d'un mécanisme de gestion des comptes utilisateurs. L'objectif est de proposer un moyen d'authentification des utilisateurs et de leur permettre de se connecter à leur compte. Il existe de nombreuses solutions pour mettre en place un tel mécanisme. Nous proposons ici une solution simple qui prend en compte les préconisations de base en matière de sécurité. Attention, cette partie ne prétend en aucun cas être suffisante pour mettre en place un système robuste et fiable de gestion des comptes. L'objet est de présenter les notions de base.

Un compte utilisateur est classiquement défini par un nom d'utilisateur (username) et un mot de passe (password). Pour améliorer la sécurité, il est fortement déconseillé de stocker le mot de passe dans une base de données. Il vaut mieux stocker un hash du mot de passe. Un hash est une clé obtenue après l'application d'un algorithme cryptographique tel que SAH-256. De plus, afin que deux mots de passe similaires ne soient pas représentés par le même hash dans la base de données, il est conseillé d'ajouter un entier aléatoire (appelé salt en anglais) au mot de passe avant d'appliquer l'algorithme cryptographique. La base de données des comptes utilisateurs contient alors trois champs : username, salt et hash. Le Code 10.2 donne l'instruction SQL de création de la table Accounts qui contiendra les comptes utilisateur. Notons que nous avons fait le choix ici d'utiliser le type BYTEA qui permet de stocker des données binaires, ce qui est le cas pour le salt et le hash.

CREATE TABLE accounts (
    id SERIAL PRIMARY KEY,
    username VARCHAR(100) NOT NULL,
    salt BYTEA,
    hash BYTEA
);

Code 10.2 : Construction de la table Account en SQL.

Le Code 10.3 montre comment calculer un hash à partir d'un mot de passe. Nous avons fait le choix d'exploiter crypto qui est la bibliothèque de base fournie par NodeJS pour effectuer des opérations de cryptographie. La ligne 4 correspond à l'intégration de cette bibliothèque. La génération aléatoirement du salt se fait avec la méthode randomBytes. La construction du hash à partir du password et du salt se fait avec les méthodes createHash et update.

const username = "moi"; //transmis par l'utilisateur
const password = "supersecret"; //transmis par l'utilisateur

const crypto = require("crypto");
const salt = crypto.randomBytes(16).toString('hex');
const hash = crypto.createHash("sha256").update(password).update(salt).digest("hex");

Code 10.3 : Exploitation de la bibliothèque Crypto de NodeJS pour générer aléatoirement le salt et pour construire un hash à partir du password.

Dès que le salt et le hash sont obtenus il suffit alors de les stocker dans notre base de données. Pour ce faire nous utilisons le framework pg présenté dans la troisième partie de ce livre et qui permet d'interagir avec la base de données Postgres. Le Code 10.4 montre la construction de la requête SQL d'insertion d'un nouveau compte utilisateur. Nous avons ici utilisé la méthode decode proposée par SQL et qui permet de traduire les données pour qu'elles respectent le format BYTEA.

const insertQuery = `INSERT INTO accounts (username, salt, hash) VALUES ('${username}', decode('${salt}','hex') , decode('${hash}','hex'));`; 
await client.query(insertQuery); 

Code 10.4 : Insertion du username, du salt et du hash dans la table des comptes utilisateur.

La vérification d'un compte se fait en comparant le username et le password transmis par un utilisateur avec ceux stockés dans la base de données. Comme l'utilisateur ne transmet que son mot de passe, il faut alors générer le hash correspondant et le comparer avec celui stocké en base. Le Code 10.5 présente le code nécessaire à ce traitement. La requête SQL utilise la méthode encode qui permet une conversion du type BYTEA. Si la requête est un succès (un utilisateur avec le bon username est dans la base), on peut alors récupérer le salt et le vrai hash (trueHash). Si la requête n'est pas un succès cela veut dire qu'il n'y a pas de compte avec le même username dans la base de données. Enfin, la comparaison du hash de la base se fait avec un autre hash qui est construit avec le password transmis par l'utilisateur. Si les deux hashs sont identiques, cela veut dire que l'utilisateur est authentifié.

const username = "moi"; //transmis par l'utilisateur
const password = "supersecret"; //transmis par l'utilisateur

const findQuery = `select username, encode(salt,'hex') as salt, encode(hash,'hex') as hash from accounts where username='${username}'`; 
const findResult = await client.query(findQuery);
const USERNAME_IS_UNKNOWN = 0;
if (parseInt(findResult.rows.length) !== USERNAME_IS_UNKNOWN) {
    const salt = findResult.rows[0].salt;
    const trueHash = findResult.rows[0].hash;
    const computedHash = crypto.createHash("sha256").update(password).update(salt).digest("hex");
    if (trueHash === computedHash) {
        //SUCCESS
    }
}

Code 10.5 : Comparaison des éléments transmis par l'utilisateur avec ceux stockés en base.

Session et Compte utilisateur

Le Code 10.6 associe le mécanisme de session avec la création des comptes utilisateur. Les sessions sont établies dès la première requête faite par un utilisateur, que celui-ci ait un compte ou pas. La gestion des sessions se fait à chaque requête pour toutes les URLs demandées (ligne 28 à 48).

La gestion des comptes utilisateur se fait avec les routes /signin (connexion au compte) et /signup (création d'un nouveau compte). Ces routes sont déclinées en GET et en POST. La partie GET sert uniquement à obtenir un formulaire permettant à l'utilisateur de saisir son username et son password. Pour améliorer la lecture du code et réduire la redondance nous avons défini une seule fonction pour générer les formulaires (fonction generateSignFormPage en ligne 124).

La partie POST de la route /signup réalise la création d'un nouveau compte utilisateur. Il faut vérifier que le username n'est pas déjà présent en base de données. Si cela n'est pas le cas, un salt est obtenu aléatoirement, puis le hash est généré. Le compte est alors créé et stocké dans la base de données.

La partie POST de la route /signin réalise l'authentification. Il faut comparer le hash qui est stocké en base de données avec celui qui est généré par le password transmis par l'utilisateur. C'est à la ligne 97 que se trouve le test qui permet de savoir si un utilisateur a transmis un bon username et un bon password. Si tel est le cas, nous ajoutons simplement le username à la session.

Les lignes 111 à 121 représentent le reste de l'application qui est ici très limitée. Nous vérifions uniquement que l'utilisateur est connecté. Si tel est le cas, nous présentons une page d'accueil avec son username. Si tel n'est pas le cas, nous lui renvoyons une page qui lui permet de se connecter ou de créer un compte. Cet exemple montre les premiers principes permettant de proposer une gestion de comptes avec un suivi de session.

const http = require("http");
const crypto = require("crypto");
const { Client } = require('pg');
const host = 'localhost';
const port = 8080;
const server = http.createServer();

const client = new Client({
    user: 'postgres', 
    password: 'root', 
    database: 'chap10',
    port : 5432 
});

client.connect()
    .then(() => {
        console.log('Connected to database');
    })
    .catch((e) => {
        console.log('Error connecting to database');
        console.log(e);
    }); 

let lastSessionId = 0;
let sessions = [];

server.on("request", (req, res) => {
    let hasCookieWithSessionId = false;
    let sessionId = undefined;
    if (req.headers['cookie'] !== undefined) {
        let sessionIdInCookie = req.headers['cookie'].split(';').find(item => item.trim().startsWith('session-id'));
        if (sessionIdInCookie !== undefined) {
            let sessionIdInt = parseInt(sessionIdInCookie.split('=')[1]);
            if (sessions[sessionIdInt]) {
                hasCookieWithSessionId = true;
                sessionId = sessionIdInt;
                sessions[sessionId].nbRequest++;
            }
        }
    }
    if (!hasCookieWithSessionId) {
        lastSessionId++;
        res.setHeader('Set-Cookie', `session-id=${lastSessionId}`);
        sessionId = lastSessionId;
        sessions[lastSessionId] = {
            'nbRequest': 0
        }
    }
    if (req.url === '/signup' && req.method === 'GET') {
        res.end(generateSignFormPage(true));
    } else if (req.url === '/signup' && req.method === 'POST') {
        let data;
        req.on("data", (dataChunk) => {
            data += dataChunk.toString();
        });
        req.on("end", async () => {
            try {
                const params = data.split("&");
                const username = params[0].split("=")[1];
                const password = params[1].split("=")[1];
                const findQuery = `select count(username) from account where username='${username}'`; 
                const findResult = await client.query(findQuery);
                const USERNAME_IS_UNKNOWN = 0;
                if (parseInt(findResult.rows[0].count) === USERNAME_IS_UNKNOWN) {
                    const salt = crypto.randomBytes(16).toString('hex');
                    const hash = crypto.createHash("sha256").update(password).update(salt).digest("hex");
                    const insertQuery = `INSERT INTO account (username, salt, hash) VALUES ('${username}', decode('${salt}','hex') , decode('${hash}','hex'));`; 
                    await client.query(insertQuery); 
                    res.end(`<html><body><h1>Sign Up is a Success</h1><a href="/signin">You can sign in now !</a></body></html>`);
                } else {
                    res.end(`<html><body><h1>Sign UP Failure</h1><div>Username already signed up !</div><a href="/">Retry</a></body></html>`);
                }
            } catch(e) {
                console.log(e);
                res.end(`<html><body><h1>Failure</h1><a href="/">Retry</a></body></html>`);
            }
        });
    } else if (req.url === '/signin' && req.method === 'GET') {
        res.end(generateSignFormPage(false));
    } else if (req.url === '/signin' && req.method === 'POST') {
        let data;
        req.on("data", (dataChunk) => {
            data += dataChunk.toString();
        });
        req.on("end", async () => {
            try {
                const params = data.split("&");
                const username = params[0].split("=")[1];
                const password = params[1].split("=")[1];
                const findQuery = `select username, encode(salt,'hex') as salt, encode(hash,'hex') as hash from account where username='${username}'`; 
                const findResult = await client.query(findQuery);
                const USERNAME_IS_UNKNOWN = 0;
                if (parseInt(findResult.rows.length) !== USERNAME_IS_UNKNOWN) {
                    const salt = findResult.rows[0].salt;
                    const trueHash = findResult.rows[0].hash;
                    const computedHash = crypto.createHash("sha256").update(password).update(salt).digest("hex");
                    if (trueHash === computedHash) { //AUTHENTICATED
                        sessions[sessionId].username = username;
                        res.end(`<html><body><h1>Sign In Success</h1>Welcome ${username}. Visit <a href="/">our site</a> </body></html>`);
                    } else {
                        res.end(`<html><body><h1>Sign IN Failure</h1> Wrong Password ! <a href="/signin">Retry</a></body></html>`);
                    }
                } else {
                    res.end(`<html><body><h1>Sign IN Failure</h1> Wrong Username ! <a href="/signin">Retry</a></body></html>`);
                }
            } catch(e) {
                console.log(e);
                res.end(`<html><body><h1>Something goes wrong</h1> <a href="/">Retry</a></body></html>`);
            }
        });
    } else {
        if (sessions[sessionId].username) {
            res.end(`<html><body><h1>Welcome ${sessions[sessionId].username}</h1>Visit <a href="/">our site</a> </body></html>`);
        } else {
            let html = `<html><body><h1>Please SignIn or SignUp !</h1>
            <div><a href="/signup">SignUp with username and password</a></div>
            <div><a href="/signin">Sign in  !</a></div>
            </body></html>`;
            res.end(html);
        }    
    }
});

function generateSignFormPage(up) {
    let signWhat = up ? 'signup' : 'signin';
    return `<html><body><h1>${signWhat}</h1>
            <form action='/${signWhat}' method="POST">
                <label for="username">Username: </label>
                <input type="text" name="username" id="username" required>
                <label for="username">Password: </label>
                <input type="password" name="password" id="password" required>
                <input type="submit" value="${signWhat}!">
            </form>
            </body></html>`;
}

server.listen(port, host, () => {
    console.log(`Server running at http://${host}:${port}/`);
});

Code 10.6 : Comptes utilisateurs et session.

Ce qu'il faut retenir

Ce chapitre présente le concept de session et une façon de gérer les comptes utilisateurs. Il faut retenir les trois points suivants:

  • HTTP est un protocole sans état (stateless) qui ne propose aucun mécanisme natif permettant de gérer les sessions ou les comptes utilisateur.
  • Les sessions sont mises en place du côté des serveurs web. Pour établir un lien avec les navigateurs web il faut alors identifier de manière unique les sessions et demander aux navigateurs de renvoyer l'identification de la session à chaque requête. Pour répondre à ce besoin, il est possible d'utiliser les cookies qui sont des petits fichiers textuels créés par les navigateurs à la demande des serveurs web.
  • La gestion des comptes nécessite la mise en place d'une base de données qui stocke uniquement un hash du password obtenu par l'application d'algorithme de cryptographie. La vérification d'un mot de passe nécessite de calculer un hash et de vérifier qu'il est bien conforme à celui stocké en base de données.

Pour s'exercer

Questions de cours

  1. Qu'est-ce qu'une session ?

    • a) Un mécanisme mis en place par les serveurs web pour savoir si les requêtes qu'ils reçoivent viennent d'un même client.
    • b) Un mécanisme mis en place par le navigateur web pour mémoriser toutes les requêtes émises vers un même serveur.
    • c) Un mécanisme supporté par HTTP pour garder une relation entre un client et un serveur.
  2. A quoi sert un identifiant de session ?

    • a) A retrouver une session dans un tableau de sessions.
    • b) A ne pas reconstruire une session pour un navigateur alors qu'elle existe déjà sur le serveur.
    • c) A garder un lien entre le serveur et le navigateur. Le serveur donne l'identifiant de session au navigateur et lui demande de le renvoyer à chaque requête afin de retrouver la session.
  3. Qu'est-ce qu'un cookie ?

    • a) Un fichier déposé par un serveur par un hacker pour montrer qu'il a hacker le site web.
    • b) Un code gardé en mémoire par le navigateur qui maintient la session avec le serveur web.
    • c) Un petit fichier stocké sur le navigateur à la demande du serveur et dont le contenu est envoyé dans toutes les requêtes envoyées vers le serveur.
  4. Pourquoi les cookies sont utilisés avec les sessions ?

    • a) Car ils permettent aux serveurs web de stocker des identifiants de session sur les navigateurs web mais aussi de faire en sorte que les navigateurs transmettent les identifiants de session à chaque fois qu'ils envoient des requêtes.
    • b) Car ils permettent aux navigateurs web d'envoyer aux serveurs toutes leurs informations personnelles. Grâce à ces informations, les serveurs web peuvent retrouver les sessions.
    • c) Car ils stockent les identifiants de toutes les requêtes émises vers un serveur web. Grâce à ces identifiants de requêtes le serveur web peut retrouver la session du navigateur web.
  5. Faut-il stocker le login et le mot de passe des utilisateurs dans une base de données ?

    • a) Oui, sinon on ne peut pas vérifier que le mot de passe transmis par l'utilisateur est le bon.
    • b) Non, il faut stocker un hash du mot de passe. Sinon on pourrait attaquer la base de données et connaitre tous les mots de passe.
    • c) Non, il ne faut jamais stocker le mot de passe. Il suffit de le garder en session.

Réponses: 1-a, 2-c, 3-c, 4-a, 5-b

Exercice 1 - Dark Mode - White Mode

L'objectif de cet exercice est de proposer un site web avec deux modes d'affichage possibles (dark et white).

L'utilisateur peut choisir sa préférence d'affichage. Cette préférence doit être stockée dans un cookie.

  1. Proposez un site web avec une seule page web qui contient un titre et du texte (choisissez le titre et le texte que vous voulez). Vous ferez en sorte que cette page web soit générée dynamiquement par le site web et qu'elle soit fournie quelle que soit l'URL demandée.

  2. Ajoutez directement dans la page web un style avec deux règles CSS (balise <style> pour ajouter le style dans la page web). La première règle cible la classe dark et définit black comme couleur de fond et white comme couleur du texte. La deuxième règle cible la classe white et définit les couleurs inverses. Par défaut, la page web est en mode white (la balise <body> a pour classe white).

  3. Ajoutez dans votre serveur web un mécanisme de session avec cookie qui met dans le cookie le mode d'affichage choisi (clé mode, valeur dark ou white). Vérifiez que votre cookie contient bien la clé mode et que la valeur white est bien celle stockée par défaut (quand on vient la première fois sur la page web).

  4. Modifiez votre serveur web pour que la valeur de la classe de la balise <body> soit égale à la valeur du mode d'affichage dans le cookie. Il faut récupérer le cookie, retrouver la valeur de la clé mode et faire en sorte que cette valeur soit affectée à la classe de la balise <body>.

  5. Ajoutez enfin une nouvelle route à votre serveur web (route GET /mode) qui permet de permuter le mode d'affichage (dark devient white et réciproquement). Cette route doit de plus demander à stocker la nouvelle valeur du mode dans le cookie du navigateur. Enfin, cette route doit renvoyer une redirection vers la page principale grâce au mécanisme HTTP (code de statut 302). L'utilisateur devrait alors voir la page principale avec le nouveau mode d'affichage.

  6. Intégrez dans votre page web un lien hypertexte (balise <a>) vers la route /mode afin que l'utilisateur puisse changer de mode en cliquant sur le lien.

Projet - mur d'images

Nous allons reprendre notre projet qui permet d'afficher des images (voir Chapitre 9, projet).

L'objectif est ici d'ajouter des comptes utilisateur à notre application. Un nouvel utilisateur doit pouvoir s'enregistrer, se connecter et se déconnecter.

Vous pouvez voir le résultat attendu ici.

Récupérez le code que vous avez réalisé dans le chapitre 9 et copiez-le dans un nouveau répertoire nommé chap10-projet.

  1. Ajoutez à votre projet la gestion des cookies pour mettre en place des sessions.

  2. Ajoutez les routes /signup et /signin pour qu'un nouvel utilisateur puisse s'enregistrer et se connecter. Vous ajouterez la table accounts à votre base de données. Vous ajouterez aussi les pages web permettant l'enregistrement ainsi que la connexion.

  3. Intégrez dans la page d'accueil de votre application les liens hypertexte vers les pages permettant l'enregistrement de comptes et la connexion. Vous ferez en sorte que ces liens se situent en haut de la page, sur la droite.

  4. Modifiez la page d'accueil pour que les liens d'enregistrement de compte et de connexion disparaissent quand l'utilisateur est connecté.

  5. Modifiez la page d'accueil pour que le nom de la personne connectée apparaisse lorsqu'elle est connectée.

  6. Ajoutez une route de déconnexion et intégrez un lien hypertexte dans la page d'accueil. Ce lien ne devra être visible que si la personne est connectée.

On souhaite maintenant permettre à une personne connectée de liker des photos.

Vous pouvez voir le résultat attendu ici.

  1. Modifiez le mur des images pour ajouter un lien like sous chaque image (balise <a> sous ). L'URL d'un lien doit correspondre à /like/i où i correspond à l'id de l'image. Vous ferez en sorte que ces liens ne soient visibles que si la personne est connectée.

  2. Ajoutez à votre serveur la route /like/i qui permet de gérer l'ajout de like. Pour ce faire, vous devez ajouter une nouvelle table à votre base de données (accounts_images_like). Recevoir une requête sur /like/i doit ajouter une ligne dans cette table.

  3. Modifiez le mur des images pour montrer les images qui ont été likées par une personne (uniquement si cette personne est connectée). Il s'agit de faire une requête sur la table accounts_images_likes pour afficher liked sous les images qui ont été likées. Vous ferez en sorte que les images likées ne puissent pas être likées à nouveau (le lien hypertexte vers /like/i ne devra pas être affiché).