Sails.js : JWT API Authentification

Ce post est destiné aux développeurs d'API's REST avec le framework Sails.js. L'objectif est de créer une authentification avec un Json Web Token pour une API REST.
Cet article est réalisé avec la version 1.0 de Sails.

Vous devez au préalable avoir une app Sails connecté à votre base de données. Dans mon exemple la BDD contient une table user comme ci-dessous :

id
int(11) Auto Increment
email
varchar(200)
passwordvarchar(200)
createdAt

updatedAt

Pour hasher les mots de passe nous allons utiliser bcrypt.

npm install bcryptjs -s
Pour créer et gérer en toute transparence notre token (JWT), nous allons utiliser le paquet du même nom :

npm install jsonwebtoken -s

Si vous n'avez pas encore de Model et Controller pour votre table d'utilisateur, vous pouvez utiliser la commande suivante pour générer automatiquement les deux fichiers (Model et Controller).

sails generate api user

Cette commande vous permet de créer le Model dans /app/models/User.js et le Controller dans /app/controllers/UserController.js.

Model

Rendez-vous dans votre fichier User.js dans /api/models et ajouter son contenu :

/**
* User.js
*
* @description :: A model definition represents a database table/collection.
* @docs        :: https://sailsjs.com/docs/concepts/models-and-orm/models
*/

var bcrypt = require("bcryptjs");

module.exports = {
  attributes: {

    //  ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦  ╦╔═╗╔═╗
    //  ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
   //  ╩  ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝

    password: 'string',
    email: 'string',

    //  ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
    //  ║╣ ║║║╠╩╗║╣  ║║╚═╗
    //  ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝


    //  ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
    //  ╠═╣╚═╗╚═╗║ ║║  ║╠═╣ ║ ║║ ║║║║╚═╗
    //  ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝


  },

  customToJSON: function() {
    // Retourne une copie du résultat sans le mot de passe
    return _.omit(this, ['password'])
  },

  beforeCreate: function(values, cb) {
    // Hash le password avant chaque création
    bcrypt.hash(values.password, 10, function(err, hash) {
      if (err) return cb(err);
      values.password = hash;
      cb();
    });
  }
};

Maintenant que notre User Model est créé vous pouvez déjà tester la création d'un utilisateur et vérifier que le mot de passe est bien hashé.

Controller

Nous allons maintenant créer les méthodes de notre API.

  • Login : Récupère l'email et le password en POST et connecte l'utilisateur
  • Token : Rafraîchit le dernier token envoyé et met à jour la date d'expiration

Passons maintenant au UserController.js dans /api/controllers pour créer ces fonctions :

/**
 * UserController
 *
 * @description :: Server-side actions for handling incoming requests.
 * @help        :: See https://sailsjs.com/docs/concepts/actions
 */


var jwt = require("jsonwebtoken");
var bcrypt = require("bcryptjs");

module.exports = {

  login: function(req, res) {
    if (!req.param('email') || !req.param('password')) {
      return res.serverError("No field should be empty.");
    }

    User.findOne({
      email: req.param('email')
    }).exec(function callback(err, user) {
      if (err) return res.serverError(err);
      if (!user) return res.serverError("User not found, please sign up.");

      // check password
      bcrypt.compare(req.param('password'), user.password, function(error, matched) {
        if (error) return res.serverError(error);
        if (!matched) return res.serverError("Invalid password.");

        user.token = jwt.sign(user.toJSON(), "votre clé secrète ici", {
          expiresIn: '7d'
        });

        res.ok(user);
      });

    });
  },

  token: function(req, res) {
    User.findOne(req.user.id).exec(function callback(error, user) {
      if (error) return res.serverError(error);
      if (!user) return res.serverError("User not found");

      user.token = jwt.sign(user.toJSON(), "votre clé secrète ici", {
        expiresIn: '7d'
      });
      res.ok(user);
    });
  },
};

Policies

Nous allons maintenant créer une règle de policies (autrement dit un middleware). Ce fichier sera executé pour chaques requètes le demandant et sera la barrière de connexion.
Créez un fichier isAuth.js dans /app/policies. Ajoutez-y la logique de notre règle :

var jwt = require("jsonwebtoken");

module.exports = function(req, res, next) {
var bearerToken;
var bearerHeader = req.headers["authorization"];
if (typeof bearerHeader !== 'undefined') {
var bearer = bearerHeader.split(" ");
bearerToken = bearer[1];

if (bearer[0] !== "Bearer") {
return res.forbidden("bearer not understood");
}

// on vérifie si le token est bon
jwt.verify(bearerToken, "votre clé secrète ici", function(err, decoded) {
if (err) {
sails.log("verification error", err);
if (err.name === "TokenExpiredError")
return res.forbidden("Session timed out, please login again");
else
return res.forbidden("Error authenticating, please login again");
}


User.findOne(decoded.id).exec(function callback(error, user) {
if (error) return res.serverError(err);
if (!user) return res.serverError("User not found");

req.user = user;
next();
});

});

} else {
return res.forbidden("No token provided");
}
};

Maintenant que notre policie est crée, nous allons configurer les méthodes qui devront la consommer. Pour cela rendez-vous dans le fichier /config/policies.js.

/**
* Policy Mappings
* (sails.config.policies)
*
* Policies are simple functions which run **before** your actions.
*
* For more information on configuring policies, check out:
* https://sailsjs.com/docs/concepts/policies
*/


module.exports.policies = {
UserController: {
update: 'isAuth',
destroy: 'isAuth',
token: 'isAuth'
}
};

Cette règle rend les méthodes update, destroy et token inaccessibles sans token.

Routes

Enfin il ne nous reste plus qu'à créer les routes correspondantes pour nos deux méthodes. Rendez-vous dans /config/routes.js.

/**
* Route Mappings
* (sails.config.routes)
*
* Your routes tell Sails what to do each time it receives a request.
*
* For more information on configuring custom routes, check out:
* https://sailsjs.com/anatomy/config/routes-js
*/


module.exports.routes = {
'post /user/login': 'UserController.login',
'get /user/token': 'UserController.token',
};

Test

Et voilà ! Vous pouvez maintenant tester le bon fonctionnement avec Postman par exemple.

  • Test de la connexion après avoir créé un utilisateur test

  • Test de la mise à jour d'un token

Merci d'avoir suivi cet article jusqu'au bout ! ❤️

Commentaires