:::: MENU ::::

Sécuriser une SPA partie1, le serveur avec expressJS

Cet article fait partie d’une série consacrée à l’authentification d’une application Web :

Les bases de l’authentification web

Sécuriser une SPA partie1, le serveur avec expressJS

Sécuriser une SPA partie2, le client sous AngularJs

Dans le précédent article, on a vu les principes de base de la sécurisation d’une application web avec la session et les tokens, on va maintenant commencer à construire notre application.

Une question de choix

Deux possibilités s’offrent à nous .

  • Full SPA : On charge toute l’application, le routage des vues se fait en fonction des droits de l’utilisateur.
  • Semi SPA : On affiche à l’utilisateur une page de login uniquement. Si il possède l’accès il est redirigé vers l’application et on charge le reste des composants. Il peut ensuite y avoir une restriction des webservices comme pour le cas où on charge l’ensemble de l’application.

 

Dans les deux cas, notre application finale est une SPA et l’appel au ressources se fait par une API. C’est cette partie du serveur qu’il faut sécuriser. Si une ressource ne doit pas être accessible à l’utilisateur qui le demande ( public ou de droit insuffisant ) alors on renverra des codes d’erreur http , typiquement 401 : Unauthorized

Quitte à faire dans le SPA, autant y aller jusqu’au bout donc partons sur la solution full.

Définir une politique de sécurité

security_policy

Avant toute chose, il convient de définir ce qu’on va protéger. Un accès admin ? des données spécifiques ? Dans tous les cas, réfléchir à son API est un point crucial si ce n’est LE point à ne pas négliger. Il en va de même pour la politique de sécurité.

 

 

En ce qui nous concerne voici notre API :

/ GET Route principale de l’application
/login POST Connexion de l’utilisateur
/logout POST Déconnexion de l’utilisateur
/api/data GET Appel vers l’api publique
/api/secure/data GET Appel vers l’api sécurisée

 

Il y a deux niveaux d’utilisateur : public et connecté. L’accès à l’api publique est libre et peut être utilisé sans restrictions. La partie secure demande à ce que l’utilisateur soit connu ( en général qu’il ait un compte ) .

PassportJs pour l’authentification coté serveur

logo-passportJs

S’il y a bien une roue à réutiliser pour l’authentification c’est PassportJs . Son avantage ? Il gère un très grand nombre de systèmes d’authentification (+ de 140 ), du simple couple user / mot de passe à la plupart des réseaux sociaux ( et plus encore) et reste simple à mettre en place.

Ce middleware repose sur le principe des stratégies. On initialise une méthode d’authentification et PassportJs donne les fonctions pour connecter un utilisateur tout en gérant le système de sessions . Pour protéger des routes, il n’y a plus qu’a tester si l’utilisateur est connecté.

Cet exemple est adapté à la nouvelle configuration d’ExpressJS 4.x . Faites attention si vous testez les exemples de PassportJs de bien utiliser la bonne version car comme expliqué dans un précédent article, il y a du changement à prévoir.

Chargement des dépendances et initialisation


var express = require("express"),
 bodyParser = require("body-parser"),
 methodOverride = require("method-override"),
 session = require("express-session"),
 cookieParser = require("cookie-parser"),
 passport = require("passport"),
 LocalStrategy = require("passport-local").Strategy;
 app = express();

// notre application
app.use(express.static(__dirname + "/public"));

// Obligatoire si on veut utiliser le système des sessions, ce module
// permet à express de décoder les données contenues dans les cookies
app.use(cookieParser());

// lecture du contenu des requetes
app.use(bodyParser());

//permet l'utilisation de requetes du type DELETE et PUT
app.use(methodOverride());

// nécessaire pour l'utilisation des sessions. secret sert à signer le cookie
app.use(session({ secret: "private" }));

// Initialisation de PassportJs ansi que du système de session
app.use(passport.initialize());
app.use(passport.session());

 

 Configuration de la stratégie d’authentification et de la session

Le système d’authentification le plus simple est basé sur un couple login / pass . Pour la gestion des session, seul l’id de l’utilisateur est enregistré coté client . Pour les autres fonctions findByUsername, findById, cela peut correspondre à des appels en base par exemple. Voir le code au complet sur GitHub.

passport.use(new LocalStrategy(
 function(username, password, done) {

 process.nextTick(function () {

 findByUsername(username, function(err, user) {
   if (err) { return done(err); }
   if (!user) { return done(null, false, { message: "Utilisateur inconnu : " + username }); }
   if (user.password !== password) { return done(null, false, { message: "Mot de passe invalide" }); }
   return done(null, user);
   });
 });
 }
));

passport.serializeUser(function(user, done) {
 done(null, user.id);
});

passport.deserializeUser(function(id, done) {
 findById(id, function (err, user) {
 done(err, user);
 });
});

 

Déclaration des routes


var auth = function(req, res, next) {
 if (!req.isAuthenticated()) {
 res.send(401);
 } else {
 next();
 }
};

app.get("/api/secure/data", auth, function(req, res, next) {
 res.send({content:"je suis une donnée protégée"});
});

app.get("/api/data", function(req, res, next) {
 res.send({content:"je suis une donnée publique"});
});

app.post("/login", function(req, res, next) {
 passport.authenticate("local", function(err, user, info) {
 if (err) { return next(err); }
 if (!user) {
 return res.send(401) ;
 }
 req.logIn(user, function(err) {
 if (err) { return next(err); }
 return res.end(JSON.stringify(user));
 });
 })(req, res, next);
});

app.post("/logout", function(req, res) {
 req.logOut();
 res.send(200);
});

// On redirige toutes les autres routes vers notre application
app.get("/*", function(req, res, next) {
 res.sendfile("./public/index.html");
});

A ce stade on peut tester notre application avec un formulaire basique .  ( Si vous voulez vous amuser il y en a un en commentaire dans index.html ).

Un mot sur les Tokens

J’en parlais dans l’article d’introdution, il est possible d’utiliser les jetons pour sécuriser une application Web. Je ne rentrerai pas dans les détails de l’implémentation de ce système pour la simple raison qu’il s’agit de remplacer le middleware d’authentification par celui gérant les token. Je vous conseille d’aller creuser du coté de express-jwt qui fera très bien le travail.

 

Dans le prochain article, on verra comment construire un client avec AngularJs gérant l’authentification que l’on vient de construire sur le serveur.


8 Comments

    • Répondre maxdow |

      C’est ce qu’on appelle single page application. En gros une application web qui ne rafraîchi que les données et dont le code s’exécute dans une seule et même page. N’hésite pas à lire le premier article de la série pour comprendre la différence en terme d’échanges client /serveur

        • Répondre maxdow |

          Bonsoir, désolé de la réponse tardive

          Le fait d’avoir un site/application SPA avec bcp de fonctionnalités n’est pas génant. La problématique est plutot : est ce qu’il est possible de créer une application complexe, maintenable et évolutive en javascript? La réponse est oui, aujourd’hui cela est possible. Il y a les outils, la maturité du langage, l’existence de bonnes pratiques. Comme pour tout langage le javascript à des faiblesses mais je pense qu’elles sont identifiées et qu’on peut largement trouver de quoi les éviter.

  • Répondre Nicolas Liveris |

    Bonjour, j’aimerai comprendre la fonction process.nextTick(). Il y a bien de la doc en anglais mais je le comprend mal. Pourriez-vous m’expliqué rapidement ?

    • Répondre maxdow |

      Bonsoir,

      en fait nextTick est dans l’exemple un peu là pour faire jolie. Si on regarde la doc : « Once the current event loop turn runs to completion, call the callback function »

      Ce qui signifie qu’on va forcer l’exécution de la fonction au prochain cycle . De cette manière on réalise un traitement qui est vraiment asynchrone, car dans l’exemple la récupération des infos se fait en local sur un tableau et est donc synchrone mais dans un cas d’utilisation normal on aurait soit une lecture par fichier ou alors plus couramment en base, donc un comportement asynchrone.

      Pour résumer nextTick dans l’exemple simule un comportement asynchrone.

      • Répondre Nicolas Liveris |

        Super j’ai saisi ! J’ai un accès à une base de donnée donc je peux retirer cette fonctions. Merci beaucoup

  • Répondre walid |

    Bonjour,
    l’utilisation du full n’est pas très lourde? est ce que ça impacte bcp les performance ??
    Cdt

So, what do you think ?