Aller au contenu principal

Requêtes avec Token

Dans le monde du développement web, la sécurité des échanges entre le client et le serveur est cruciale. L'utilisation de tokens, tels que ceux fournis par Json Web Tokens, offre une solution robuste pour authentifier les utilisateurs et restreindre l'accès aux ressources. Dans le cadre de ce cours, nous utiliserons json-server-auth pour simplifier la partie serveur. Si vous souhaitez creuser, json-server-auth vous pouvez lire la documentation officielle.

Installation de json-server-auth

Si vous utilisez votre propre ordinateur

Pour commencer, installez json-server et json-server-auth à l'aide de la commande npm suivante :

npm install -D json-server json-server-auth

à l'IUT ce package est déjà installé sur les machines.

Configuration du serveur avec json-server-auth

Créez un fichier db.json comme ceci.

{
"users": []
}

À la fin de ce tutoriel, le fichier db.json ressemblera à quelque chose proche de cela. Notez que le mot de passe est "haché" en base par le serveur.

{
"users": [
{
"email": "olivier@mail.com",
"password": "$2a$10$ggcilMNszjWtJgUL53jITeq7qugPD3hYrJgn9/AEi88I4RY9o1c/y",
"firstname": "Olivier",
"lastname": "Monge",
"age": 32,
"id": 3
}
]
}

json-server-auth permet de configurer l'accès aux différentes routes en configurant le fichier routes.json.

{
"/users*": "/660/users$1"
}

Ici on a indiqué que tous les endpoint sur l'URL users* nécessitent d'être un utilisateur authentifié pour faire une requête (en écriture ou lecture) sur ce endPoint.

Vous pouvez maintenant lancer votre serveur.

json-server-auth db.json -r routes.json
Si la commande précédente ne fonctionne pas

Sur votre ordinateur, il est possible que la précédente commande ne fonctionne pas car il ne trouve pas json-server-auth. Testez celle-ci (en supposant que vous avez fait une installation locale de json-server-auth).

json-server db.json -p 3000 -m ./node_modules/json-server-auth -r routes.json

Par défaut, le serveur met à disposition différents endpoint :

  • /register pour créer un utilisateur
  • /sigin pour s'authentifier
  • /users pour afficher l'ensemble des utilisateurs et leurs données associées.

Opérations côté serveur depuis le client

Pour bien comprendre la mécanique, nous allons dans un premier temps utiliser curl pour communiquer avec le serveur. Nous verrons ensuite la même chose, mais en JS.

Création d'un utilisateur

Pour créer un utilisateur, utilisez la commande curl suivante (je suppose que votre serveur tourne sur le port 3000):

curl -X POST -H "Content-Type: application/json" -d '{"email": "nouvel_utilisateur@example.com", "password": "mot_de_passe"}' http://localhost:3000/register

Le serveur met à jour le fichier db.json. Il devrait ressembler à ça :

{
"email": "nouvel_utilisateur@example.com",
"password": "$2a$10$h/lBNsdyua7mkXptMdXMuOsuwmiEWkeHjOoZnTHGwTcODiSz3jM66",
"id": 2
}

Vous remarquerez que le mot de passe est haché.

Connexion d'un utilisateur

Pour se connecter, utilisez la commande curl suivante :

curl -X POST -H "Content-Type: application/json" -d '{"email": "nouvel_utilisateur@example.com", "password": "mot_de_passe"}' http://localhost:3000/signin

Vous obtiendrez un token d'accès à utiliser dans les requêtes ultérieures. C'est ce token qui permettra au serveur de confirmer que vous vous êtes bien, au préalable, authentifié et que donc vous pouvez faire des requêtes sur le endpoint /users. Par défaut, le serveur json-server-auth fournit un token valide 1H.

{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"email": "nouvel_utilisateur@example.com",
"id": 2
}
}

Utilisation du token côté client

Le token est essentiel pour que le serveur reconnaisse l'utilisateur. Intégrez-le dans vos requêtes comme suit :

Requête non autorisée (sans token)

La requête suivante ne fonctionnera pas sans un token valide et retournera "Missing authorization header".

curl -X GET -H "Content-Type: application/json" http://localhost:3000/users

Requête autorisée (avec token)

Utilisez le token obtenu lors de la connexion dans la requête pour accéder aux informations utilisateur :

  • pour récupérer toutes les entrées de users
curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...." http://localhost:3000/users

pour récupérer une entrée précise :

curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...." http://localhost:3000/users/2

Voici par exemple ce que vous retourne la précédente commande curl sur l'utilisateur d'id numéro 2.

{
"email": "f61doxuit@example.com",
"password": "$2a$10$w22Ei.isSDjN9C4sHQguhuBYwTuGwzwbHfjQGCeSD4FvcvAzcFVd2",
"id": 2
}

Implémentation en JavaScript

Pour effectuer les mêmes opérations en JavaScript, vous pouvez utiliser la fonction fetch.

Création d'un utilisateur en JS

const createUser = async () => {
const apiUrl = 'http://localhost:3000/users';

// Données à envoyer
const userData = {
email: 'nicholas@mail.com',
password: 'mot_de_passe',
};

// Configuration de la requête
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
};

try {
// Envoi de la requête avec await
const response = await fetch(apiUrl, requestOptions);

if (!response.ok) {
throw new Error(`Erreur HTTP! Statut : ${response.status}`);
}

const data = await response.json();
console.log('Réponse du serveur :', data);
} catch (error) {
console.error('Erreur lors de la requête :', error);
}
}

Connexion d'un utilisateur en JS

Lorsque la connexion est réussie, il faut que le client sauvegarde le token (qui par défaut sera valide 1H côté serveur). Il existe plusieurs manières de sauvegarder ce token :

  • dans le sessionStorage : le token est effacé lorsque la session du navigateur se termine (c'est-à-dire lorsque le navigateur est fermé)
// Stoker le token dans le sessionStorage
sessionStorage.setItem('access_token', data.accessToken);
// Récupérer le token depuis le sessionStorage
const token = sessionStorage.getItem('access_token');
  • dans le localStorage : le token n'est jamais effacé
// Stoker le token dans le sessionStorage
localStorage.setItem('access_token', data.accessToken);
// Récupérer le token depuis le sessionStorage
const token = localStorage.getItem('access_token');
  • via un cookie : Lorsque vous stockez un cookie en utilisant document.cookie, la durée de vie du cookie est contrôlée par ses attributs tels que la date d'expiration. Si vous ne spécifiez pas explicitement une date d'expiration pour le cookie, il devient une session cookie, ce qui signifie qu'il sera automatiquement supprimé lorsque la session du navigateur se termine. En ce qui concerne la navigation privée, les cookies de session (ceux sans date d'expiration spécifiée) sont souvent traités différemment. Dans de nombreux navigateurs, les cookies de session ne sont pas persistants pendant une session de navigation privée. Cela signifie que lorsqu'une fenêtre de navigation privée est fermée, tous les cookies de session créés pendant cette session sont supprimés. Les cookies persistants (avec une date d'expiration spécifiée) peuvent être conservés même en navigation privée si la date d'expiration est dans le futur. Cependant, la conservation des cookies pendant la navigation privée peut varier en fonction des navigateurs et de leurs paramètres de confidentialité. Certains navigateurs peuvent supprimer tous les cookies à la fin de la session de navigation privée, même s'ils ont une date d'expiration.
// Ajouter une date d'expiration (par exemple, une heure à partir de maintenant)
const expirationDate = new Date();
expirationDate.setHours(expirationDate.getHours() + 1);
document.cookie = `access_token=${data.accessToken}; expires=${expirationDate.toUTCString()}; path=/`;
//pour récupérer un cookie précis :
const monToken = getCookie('access_token');

On pourrait également stocker le token dans une variable toute simple. Si la page est actualisée, le contexte est réinitialisé, et vous pourriez perdre l'information d'authentification. Cela dépend du comportement spécifique de votre application.

danger

Toutes les solutions proposées pour stocker le token sont vulnérables aux failles XSS. Le Cross-Site Scripting (XSS) est une vulnérabilité de sécurité courante dans les applications web. Elle survient lorsqu'un attaquant réussit à injecter du code JavaScript malveillant dans des pages web consultées par d'autres utilisateurs. Cette injection non autorisée de code peut se produire lorsque l'application web ne gère pas correctement les données entrées par l'utilisateur avant de les afficher sur la page. Cette injection permet à la personne malveillante de récupérer les cookies, les variables session (et donc dons notre cas le token)

Voici ce que donne le code de connexion (avec un cookie).

const connectUser = async () => {
try {
const email = 'nicholas@mail.com';
//ATTENTION faille de sécurité ici (cf remarque plus bas)
//Cet exemple est simplement pour faire un test en local et comprendre la mécanique des requêtes avec token
//merci de ne JAMAIS faire ça "en vrai"
const password = 'mot_de_passe';

// Générer la requête avec les informations d'authentification
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
};
// Effectuer la requête avec await
const response = await fetch('http://localhost:3000/login', requestOptions);
// Vérifier le statut de la réponse
if (!response.ok) {
throw new Error(`Erreur HTTP! Statut : ${response.status}`);
}
// Extraire les données JSON de la réponse
const data = await response.json();
console.log('Réponse du serveur :', data);
// Stocker le token dans un cookie
document.cookie = `access_token=${data.accessToken}; path=/`;
} catch (error) {
console.error('Erreur lors de la requête :', error);
}
}

danger

Vous aurez remarqué que dans l'exemple précédent, le mot de passe part en clair.

On n'écrit JAMAIS le mot de passe en clair comme dans le code précédent. Même si vous passez par un champ input de type password (l'utilisateur saisit son mot de passe et on voit apparaitre des petits ronds à la place des caractères), cela ne sécurise rien du tout. N'importe qui ayant accès au réseau sur lequel vous êtes connecté, peut voir passer la trame et avoir accès au mot de passe. C'est évidemment une énorme faille de sécurité. Pour sécuriser la transmission des informations d'authentification, vous pouvez utiliser le protocole HTTPS (HTTP Secure). HTTPS chiffre les données entre le client et le serveur, empêchant ainsi les tiers d'intercepter et de lire les informations sensibles, y compris les noms d'utilisateur et les mots de passe. Cet aspect sera étudié en troisième année.

Récupération des informations utilisateur en JS

Le serveur attend que vous lui fournissiez un token valide pour pouvoir faire une requête sur le endpoint users.

const getUserData = async () => {
try {
// Récupérer le token du local storage
const accessToken = getCookie('access_token') || localStorage.getItem('access_token');

// Vérifier si le token existe
if (!accessToken) {
throw new Error('Token non trouvé. Connectez-vous d\'abord.');
}

// Générer les en-têtes de la requête
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
};

// Effectuer la requête avec await
const response = await fetch('http://localhost:3000/users', requestOptions);

// Vérifier le statut de la réponse
if (!response.ok) {
throw new Error(`Erreur HTTP! Statut : ${response.status}`);
}

// Extraire les données JSON de la réponse
const userData = await response.json();

console.log('Données utilisateur :', userData);
} catch (error) {
console.error('Erreur lors de la requête :', error);
}
};