Archives Wild Code School
React - L'Odyssey d'Homer

080 - Papier Sil Vou Plé

Quête 9 sur 11 · 1 image archivée

Tu commences à avoir une application qui prend forme. Par contre il reste encore beaucoup de choses à implémenter comme par exemple l'authentification.

En passant si tu affiches ta base de données tu devrais avoir un résultat du type :

id          email                           password                   name        lastname
----------  ------------------------------  -------------------------  ----------  ----------
1           homersimpson@wildcodeschool.fr  mon-mot-de-passe-en-clair  Homer       Simpson
2           gregory@wildcodeschool.fr       azerty                     Greg        Ory

Tu dois avoir larme à l'oeil en regardant l'output ci-dessus !

Il y a de gros problèmes de sécurité dans l'application actuelle et tu vas devoir l'améliorer. En l'occurence ici le mot de passe en clair est un gros problème. Nous allons corriger ça et proposer un système d'authentification lors de cette quête.

Objectifs

  • L'objectif de cette quête est l'implémentation de l'authentification coté back ! Avant cela tu vas voir comment on encrypte un password et suivra le mécanisme de vérification d'un email/password. A la fin de la quête tu pourras t'amuser avec Postman à t'enregistrer et t'authentifier !

Les contes de la crypto

Maintenant que tu t'es rincé l'oeil à la Javel, tu vas vite corriger l'histoire des mots de passe en clair. Et cela en cryptant le mot de passe avant de le stocker en base de données. Le mot de passe doit n'être connu que de l'utilisateur. On utilise pour cela bcrypt qui est une librairie de cryptage.

cryptage : Transformation d'un message en clair en un message codé compréhensible seulement par qui dispose du code - larousse.fr

Ici tu vas transformer un mot de passe par exemple youpi en hash par exemple 2b10$m9WRdX86.XChZZkaZjU6GeVH9rytk08WEPMvdCY7zGKpZxcowrgOi .

npm install --save bcrypt

Son utilisation est simple et peut être utilisé en synchrone ou en asynchrone. En synchrone cela donne :

javascript
// je crypte myPassword qui devient un 'hash'
let hash = bcrypt.hashSync('myPassword', 10);
// je compare un mot de passe avec le 'hash', j'obtiens true ou false s'ils sont identique ou non
let isSame = bcrypt.compareSync('somePassword', hash)

Donc dans la route /auth/signup il ne faut plus insérer req.body.password mais son hash. Dans la suite du projet lorsque tu vas vouloir vérifier si le mot de passe est correct, tu utiliseras la fonction bcrypt.compareSync().

↗

Installation de passport

Passport est une librairie gérant l'authentification sous Node ⇒ coté backPassport vient avec un certain nombre de modules pour tout type -nous parlerons de stratégie- d'authentification : login/password, facebook, google, etc. Dans l'application on va se concentrer sur l'authentification avec login/password à l'aide du module passport-local.

npm install --save passport passport-local
↗
Documentation Passport
passportjs.org

Configuration de passport dans app.js

Ensuite tu vas devoir configurer passport en lui passant la strategy. Tu peux le mettre à la suite de la configuration d'express ou dans un fichier à part.

javascript
passport.use(new LocalStrategy(
    { 
        usernameField: 'email', 
        passwordField: 'password',
        session: false
    }, 
    function (email, password, cb) {
        //ton code ici
    }
));

La stratégie prend deux arguments :

  • config :

usernameField/passwordField ⇒ qui permet de définir le nom des variables récupérer pour l'authentification. Par défaut la config attend username et password mais tu vas utiliser plutôt email et password__session ⇒ qui permet de garder les informations de session de l'utilisateur authentifié. Tu le verras plus loin, nous n'allons pas utiliser de session donc il faut le mettre à false

  • callback avec les arguments :

email de l'utilisateur password de l'utilisateur cb callback prenant les arguments error,user et un objet info. Par exemple :

js
// si une erreur est obtenue
return cb(err)
// si les login/password ne sont pas bon
return cb(null, false, { message: 'Incorrect email ou password.' })
// si l'utilisateur est ok on retourne l'objet user
return cb(null, user)

Tu vas devoir implémenter la fonction function(email, password, cb){} afin de retourner les 3 cas possibles ci-dessus. ⇒ TIPS : il y aura du SQL là dedans

↗
Passport Local
github.com

Authentification sur /auth/signin

Tout d'abord il faut créer une nouvelle route dans le fichier auth.js pointant sur /signin.

javascript
router.post('/signin', function(req, res) {});

Passport propose la fonction authenticate() avec en paramètre le nom de la stratégie et le retour de la vérification du login/password. Elle est à mettre dans la callback de la route /signin. Elle ressemble par exemple à :

javascript
passport.authenticate('local',(err, user, info) => { 
    if(err) return res.status(500).send(err) 
    if (!user) return res.status(400).json({message: info.message});  
    return res.json({user}); 
 })(req, res)

Attention : passport va récupérer les variables spécifiées dans la construction de la stratégie { usernameField: 'email', passwordField: 'password' } soit ici req.body.email et req.body.password

⇒ A tester avec Postman, tu dois pouvoir :

  • insérer un nouvel utilisateur avec le hash du mot de passe en base de données sur /auth/signup

  • t'authentifier avec ce nouvel utilisateur sur /auth/signin

↗

Jane With Tarzan

Rappel sur le principe d'authentification et de session Pour qu'un utilisateur accède à de la donnée sécurisée il doit signifier qu'il est bien telle personne qui a les bons droits. L'utilisateur doit apporter la preuve de son identité à chaque requête ! Afin d'éviter que l'utilisateur envoie à chacune de ses requêtes son login/password, l'utilisateur va s'authentifier une première fois et va se voir attribuer un jeton (token) unique - potentiellement temporaire - qu'il va communiquer lors de ses requêtes. Coté serveur le token est stocké et lorsqu'il reçoit une requête avec un token il va vérifier sa liste afin d'identifier l'utilisateur. Pour certaines raisons cette approche pose problème. Par exemple si la session est stockée en mémoire, lorsque le serveur redémarre ou lorsqu'on passe sur un autre serveur (lors de l'utilisation répartiteur de charge), la liste des tokens est perdue et l'utilisateur se voit invité à s'authentifier à nouveau. Ou encore si la session est stocké en base de données, on peut observer une charge/latence élevé.

C'est là que le JWT (Json Web Token) rentre en scène. L'idée est d'insérer dans le token les informations nécessaires à l'identification de l'utilisateur. Le serveur crypte les informations de celui-ci et lui renvoie le hash. Le serveur ne stocke aucune information. Lors des prochains requêtes, le serveur décrypte à la volée le hash et récupère ainsi l'identité. Le serveur devient ainsi entièrement stateless (sans état).

Tout d'abord il faut installer le module jsonwebtoken, qui propose la gestion de JWT, et passport-jwt, qui intègre la gestion JWT dans le process de passport.

javascript
npm install --save passport-jwt jsonwebtoken
  1. La première chose à faire est, au moment la l'authentification, de retourner le token. On génère le token à l'aide du module jsonwebtokenet de la fonction sign. sign va encoder l'objet user à l'aide d'une phrase qu'on appelle secret.
javascript
const jwt = require('jsonwebtoken');
...
passport.authenticate('local',(err, user, info) => { 
  ...
  const token = jwt.sign(user, 'your_jwt_secret');  
  return res.json({user, token});  
})(req, res)
  1. Il faut identifier l'utilisateur à chaque requête en décodant le JWT. Pour cela tu vas devoir créer une nouvelle stratégie passport.
javascript
passport.use(
  new JWTStrategy(
    {  
      jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),  
      secretOrKey   : 'your_jwt_secret'  
    },
    function (jwtPayload, cb) {  
      return cb(null, jwtPayload);
    }
  )
);

Cette stratégie récupère le token dans le header de la requête ExtractJWT.fromAuthHeaderAsBearerToken() et décode le token à l'aide du secret. Attention : le secret doit être le même que celui qui sert à encoder le token !!. Passport nous retourne dans jwt_payload l'objet décodé ici l'email.

On vérifie les droits d'accès pour chacune des routes qui en ont besoin Pour faire un test, tu vas ajouter dans app.js en dessous de app.use('/auth', authRouter); :

javascript
app.get("/profile", passport.authenticate('jwt', { session:  false }), function (req, res) {
  res.send(req.user);
});

Ici pour accéder à la route /profile il faut être authentifié avec un token. On récupère l'information dans req.user.

Tester avec Postman

Pour vérifier que l'authentification est fonctionnelle, tu vas utiliser Postman. Voici comment l'utiliser. On s'authentifie et on récupère le token

Ensuite on insère le token dans l'onglet Authorization avec la configuration Bearer Token :

↗

Challenge

Show me your token

  • Mettre à disposition une route /auth/signin qui fournit un jwt
  • Mettre à disposition une route /profile qui n'est accessible qu'avec un token valide

Critères de validation

  • Vérifier qu'avec un mauvais email/password on obtient rien
  • Vérifier qu'avec un bon email/password on obtient un token
  • Vérifier qu'avec un mauvais token on ne soit pas autorisé à accéder à /profile
  • Vérifier qu'avec un bon token on obtient son emailen retour