Fork ce boilerplate afin de démarrer le tutoriel : https://github.com/bastienwcs/nodejs-jwt-boilerplate.
Afin de gérer l'inscription et la connexion d'utilisateurs, crée une nouvelle base de données nommée jwtcourse
et crée la table user
en respectant cette modélisation :
Dans le projet, copie le fichier .env.sample
vers .env
et modifie les variables d'environnements correspondantes à la base de données.
Pense à installer le projet avec npm install
avant de démarrer !
Créer une route en POST /register
qui va permettre la création d'un compte utilisateur.
La route doit récupérer du corps de la requête un json à la structure suivante :
{
"email": "son email",
"password": "son mot de passe"
}
Si l'email ou le mot de passe ne sont pas renseignés, renvoyer une erreur 400 'Please specify both email and password'.
Dans le cas où ils sont renseignés, faire une requête à la base de données et insérer les données dans la table user
.
Si une erreur survient lors de l'exécution de la requête SQL, renvoyer une erreur 500 avec le message d'erreur correspondant.
Si tout c'est bien passé, renvoyer un code 201 avec un json ayant la structure suivante :
{
"id": "son id",
"email": "son email",
"password": "hidden"
}
Ne pas renvoyer le mot de passe renseigné mais la chaîne de caractère "hidden"
Teste le tout avec Postman :
- POST http://localhost:8080/register
- Body / raw / JSON
- Dans le corps de la requête un JSON, par exemple :
{
"email": "[email protected]",
"password": "tacos"
}
Attention : essaie de faire l'exercice par toi-même avant de regarder la solution !
app.post('/register', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
res
.status(400)
.json({ errorMessage: 'Please specify both email and password' });
} else {
connection.query(
`INSERT INTO user(email, password) VALUES (?, ?)`,
[email, password],
(error, result) => {
if (error) {
res.status(500).json({ errorMessage: error.message });
} else {
res.status(201).json({
id: result.insertId,
email,
password: 'hidden',
});
}
}
);
}
});
Il est très dangereux de laisser le mot de passe de l'utilisateur en clair dans une base de données.
Regarde le lien suivant pour voir comment hasher le mot de passe avec la bibliothèque bcrypt : https://www.abeautifulsite.net/hashing-passwords-with-nodejs-and-bcrypt.
Installe le module bcrypt dans ton projet.
Ensuite modifie ta route /register
pour crypter le mot de passe de façon synchrone, avant qu'il ne soit enregistré dans la base de données.
Vérifie que le mot de passe est bien encrypté dans la base de donnée.
Pense à importer le module en haut de ton fichier !
Attention : essaie de faire l'exercice par toi-même avant de regarder la solution !
app.post('/register', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
res
.status(400)
.json({ errorMessage: 'Please specify both email and password' });
} else {
const hash = bcrypt.hashSync(password, 10);
connection.query(
`INSERT INTO user(email, password) VALUES (?, ?)`,
[email, hash],
(error, result) => {
if (error) {
res.status(500).json({ errorMessage: error.message });
} else {
res.status(201).json({
id: result.insertId,
email,
password: 'hidden',
});
}
}
);
}
});
Créer une route en POST /login
qui va permettre la connexion d'un compte utilisateur.
La route doit récupérer du corps de la requête un json à la structure suivante :
{
"email": "son email",
"password": "son mot de passe"
}
Si l'email ou le mot de passe ne sont pas renseignés, renvoyer une erreur 400 'Please specify both email and password'.
Dans le cas où ils sont renseignés, faire une requête à la base de données et vérifier que l'email existe bien (tester l'email uniquement, pas le mot de passe !).
Si une erreur survient lors de l'exécution de la requête SQL, renvoyer une erreur 500 avec le message d'erreur correspondant.
Si le résultat renvoyé est vide, renvoyer une erreur 403 'Invalid email'.
Si le résultat n'est pas vide, tu vas maintenant vérifier le mot de passe en utilisant la méthode compareSync
du module bcrypt. Tu peux trouver un exemple d'utilisation ici : https://www.abeautifulsite.net/hashing-passwords-with-nodejs-and-bcrypt.
Attention, il faut mettre le mot de passe en clair en premier argument et le mot de passe de la base de données en second
Si tout le mot de passe est identique, renvoyer un code 200 avec un json ayant la structure suivante :
{
"id": "son id",
"email": "son email",
"password": "hidden"
}
Sinon renvoie une erreur 403 avec le message 'Invalid password'.
Teste le tout avec Postman :
- POST http://localhost:8080/login
- Body / raw / JSON
- Dans le corps de la requête un JSON, par exemple :
{
"email": "[email protected]",
"password": "tacos"
}
Attention : essaie de faire l'exercice par toi-même avant de regarder la solution !
app.post('/login', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
res
.status(400)
.json({ errorMessage: 'Please specify both email and password' });
} else {
connection.query(
`SELECT * FROM user WHERE email=?`,
[email],
(error, result) => {
if (error) {
res.status(500).json({ errorMessage: error.message });
} else if (result.length === 0) {
res.status(403).json({ errorMessage: 'Invalid email' });
} else if (bcrypt.compareSync(password, result[0].password)) {
// Passwords match
const user = {
id: result[0].id,
email,
password: 'hidden',
};
res.status(200).json(user);
} else {
// Passwords don't match
res.status(403).json({ errorMessage: 'Invalid password' });
}
}
);
}
});
Tu rentres enfin dans le vif du sujet : la génération du JWT grâce à une clef secrète.
Commence par renseigner une clé secrète dans le fichier .env
. Tu peux générer une clé sécurisée ici : https://www.grc.com/passwords.htm.
Ensuite, tu vas utiliser le module jsonwebtoken pour effectuer la génération de la clé :
- installe le module
- utilise la méthode
sign
afin de générer un JWT, en utilisant la clé secrète des variables d'environnement. - Le payload de la clé sera le json suivant :
json { id: user.id }
- la date d'expiration
expiresIn
sera de 5 minutes.
Génère la clé juste avant de renvoyer utilisateur dans la route /login
et fait en sorte que la structure du JSON soit la suivante :
{
"user": {
"id": "son id",
"email": "son email",
"password": "hidden"
},
"token": "le token généré"
}
Attention : essaie de faire l'exercice par toi-même avant de regarder la solution !
app.post('/login', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
res
.status(400)
.json({ errorMessage: 'Please specify both email and password' });
} else {
connection.query(
`SELECT * FROM user WHERE email=?`,
[email],
(error, result) => {
if (error) {
res.status(500).json({ errorMessage: error.message });
} else if (result.length === 0) {
res.status(403).json({ errorMessage: 'Invalid email' });
} else if (bcrypt.compareSync(password, result[0].password)) {
// Passwords match
const user = {
id: result[0].id,
email,
password: 'hidden',
};
const token = jwt.sign({ id: user.id }, JWT_AUTH_SECRET, {
expiresIn: 300,
});
res.status(200).json({ user, token });
} else {
// Passwords don't match
res.status(403).json({ errorMessage: 'Invalid password' });
}
}
);
}
});
Créer une route en GET /users
qui récupère la liste des utilisateurs.
Si une erreur survient lors de l'exécution de la requête SQL, renvoyer une erreur 500 avec le message d'erreur correspondant.
Si tout c'est bien passé, renvoyer un code 200 avec un json ayant la structure suivante :
[
{
"id": 1,
"email": "[email protected]",
"password": "hidden"
},
{
"id": 2,
"email": "[email protected]",
"password": "hidden"
}
]
Pense à cacher le mot de passe de chaque utilisateur !
Attention : essaie de faire l'exercice par toi-même avant de regarder la solution !
app.get('/users', (req, res) => {
connection.query(`SELECT * FROM user`, (error, result) => {
if (error) {
res.status(500).json({ errorMessage: error.message });
} else {
res.status(200).json(
result.map((user) => {
return { ...user, password: 'hidden' };
})
);
}
});
});
Afin de protéger la route /users
pour que seuls les utilisateurs authentifiés puissent y accéder, tu vas créer un middleware qui va récupérer l'entête de la requête et y regarder la présence d'un token.
Pour cette partie là, le middleware est fourni et est à ajouter avant la route /users
:
const authenticateWithJsonWebToken = (req, res, next) => {
if (req.headers.authorization !== undefined) {
const token = req.headers.authorization.split(' ')[1];
jwt.verify(token, JWT_AUTH_SECRET, (err) => {
if (err) {
res
.status(401)
.json({ errorMessage: "you're not allowed to access these data" });
} else {
next();
}
});
} else {
res
.status(401)
.json({ errorMessage: "you're not allowed to access these data" });
}
};
Tu vas devoir modifier la route user afin qu'elle charge ce middleware, puis tester la route avec Postman en renseignant l'entête suivante : Authorization: Bearer eyJhbG.. ...8RvKts
Bien sûr, il faudra remplacer le token par celui récupéré lors de la connexion de l'utilisateur dans l'étape 4.
Attention : essaie de faire l'exercice par toi-même avant de regarder la solution !
// Token middleware
const authenticateWithJsonWebToken = (req, res, next) => {
if (req.headers.authorization !== undefined) {
const token = req.headers.authorization.split(' ')[1];
jwt.verify(token, JWT_AUTH_SECRET, (err) => {
if (err) {
res
.status(401)
.json({ errorMessage: "you're not allowed to access these data" });
} else {
next();
}
});
} else {
res
.status(401)
.json({ errorMessage: "you're not allowed to access these data" });
}
};
// Authenticated route
app.get('/users', authenticateWithJsonWebToken, (req, res) => {
connection.query(`SELECT * FROM user`, (error, result) => {
if (error) {
res.status(500).json({ errorMessage: error.message });
} else {
res.status(200).json(
result.map((user) => {
return { ...user, password: 'hidden' };
})
);
}
});
});