DEV Community 👩‍💻👨‍💻

Cover image for AUTHENTIFICATION AVEC NODE JS/EXPRESS JS
samuel mbabhazi
samuel mbabhazi

Posted on

AUTHENTIFICATION AVEC NODE JS/EXPRESS JS

APERÇU

  • C’est quoi une authentification ?
  • Authentification vs Autorisation
  • Pour quoi une Authentification ?
  • Implémentation d’une authentification dans une application node js

C’est quoi une authentification

Authentification est le processus d'identification des utilisateurs en acquérant des informations d'identification telles que l'e-mail, le mot de passe et les jetons.

Authentification vs Autorisation

L'authentification est le processus de vérification qu'un utilisateur est bien celui qu'il prétend être, tandis que l'autorisation est le processus de détermination si un utilisateur authentifié a accès à certaines parties de ton application.
exemple d'autorisation est le contenu basé sur un abonnement, votre authentification peut être effectuée en vous connectant au site Web, mais vous ne serez pas autorisé à accéder au contenu tant que vous ne vous serez pas abonné.

Pour quoi une Authentification

L'authentification de l'utilisateur est cruciale pour protéger les informations sensibles.

Implémentation d’une authentification dans une application node js

Dans le cadre d'une authentification qui implique la communication d'identifiant et de mot de passe entre le client et le serveur, il est nécessaire d'utiliser une communication chiffrée en mettant en place le protocole HTTPS.
La création d'un serveur HTTPS avec Node.js reste très similaire à celle d'un serveur HTTP. La différence se situe au niveau de l'ajout, au moment de la création du serveur, des informations nécessaires au chiffrement des communications. Il faut préciser les certificats utilisés.
Les certificats doivent être obtenus à partir d'une autorité de certification, réputée fiable. Let's Encrypt est une autorité de certification qui permet d'obtenir un certificatv gratuitement. Il est également possible d'"auto-signer" ses propres certificats (mais l'utilisation de ces certificats provoque alors une alerte de sécurité sur les navigateurs).
Pour créer un serveur HTTPS, il faut donc préciser au serveur la clef privée et le certificat associé. Le reste ne change pas par rapport à la version HTTP. On procède comme indiqué dans le code ci-dessous. On peut noter qu'il est nécessaire d'utiliser une lecture synchrone des fichiers des certificats, car il n'est pas possible de créer le serveur, et donc d'exécuter les lignes qui suivent la lecture, avant d'avoir terminé la lecture de ces fichiers.

// fichier ./secured-server.js
const https = require('https');
const fs = require('fs';

// chargement des certificats
const privateKey  = fs.readFileSync('./certs/certificat.key', 'utf8');
const certificate = fs.readFileSync('./certs/certificat.crt', 'utf8');

// création du serveur HTTPS
const credentials = {
                      key : privateKey,
                      cert : certificate
                    };
const securedHttpsServer = https.createServer(
  credentials,
  (request, response) => {
    // création et envoi de la réponse
    response.writeHead(200, {"Content-Type": "text/html"});
    response.write('<h1>HTTPS secured server</h1>');
      response.end();
  }
);

securedHttpsServer.listen(8081);


Enter fullscreen mode Exit fullscreen mode

Attention, pour accéder au serveur HTTPS, dans le navigateur, il faut explicitement mentionner le protocole utiliser et donc indiquer https://localhost:8081. Lors du premier accès, il faudra passer outre l'alerte de sécurité qui signale que le certificat n'est pas reconnu, car auto-signé.

Authentification basique

L'authentification basique permet de facilement mettre en place un contrôle d'accès basé sur un nom d'utilisateur et un mot de passe. Ce mécanisme est sans doute le plus simple possible de ce type. L'authentification basique n'utilise pas de cookie ni de mécanisme de session. Les informations d'authentification sont transmises dans l'entête HTTP dans un champ de la forme "Authorization: Basic credentials". "credentials" contient une chaîne obtenue par concaténation du nom d'utilisateur et du mot de passe séparés par ":". Cette chaîne subit un simple encodage Base64. Cette donnée n'est donc par cryptée, il faut donc utiliser l'authentification basique en combinaison avec HTTPS. Ces informations ne sont pas envoyées avec chaque requête et sont donc mises en cache par le navigateur (jusqu'à sa fermeture).
Le code mis à disposition contient un serveur simple, construit à l'aide d'Express. A titre purement illustratif.

Nous aurons a utiliser pug comme moteur de template
pour plus d'information https://pugjs.org/api/getting-started.htm

// fichier ./routes/index.js
const express = require('express');
const router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode
//fichier ./routes/protect.js
const express = require('express');
const router = express.Router();


/* GET protect page. */
router.get('/', function(req, res, next) {
  res.render('protect', { title: 'Express' });
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Le routeur gère deux chemins : "/" et "/protect". On souhaite que le premier soit accessible librement alors que le deuxieme soit protégé par une authentification basique.

Mise en place du middleware d'authentification
Le contrôle d'accès par authentification basique est géré à l'aide d'un middleware. Le principe est que les requêtes qui portent sur des chemins doivent passer par ce middleware qui soit demande à l'utilisateur de s'authentifié, lors d'un premier accès, soit vérifie que l'accès a déjà été accordé, si l'utilisateur s'est déjà authentifié.

Le middleware est défini dans le dossier ./middleware/authentifcation.js. Comme dit précédemment, l'authentification basique se base sur des informations positionnées dans la propriété "authorization" de l'entête ("header") de la requête. Il faut donc commencer par récupérer la valeur de cette propriété :

// fichier ./middleware/authentifcation.js
const authentication = (req, res, next) => {
  const authheader = req.headers.authorization;
  (...)
Enter fullscreen mode Exit fullscreen mode

Deux situations doivent alors être envisagées, selon la présence ou non de cette propriété. Si elle n'est pas présente, il faut alors envoyer en réponse une demande d'authentification en précisant l'entête WWW-Authenticate. Cette réponse est envoyée avec le statut 401 qui correspond à un accès refusé ("unauthorized").

// fichier ./middleware/authentifcation.js
const authentication = (req, res, next) => {
  (...)
  if (!authheader) {
    res.setHeader('WWW-Authenticate', 'Basic realm="mon site à moi", charset="UTF-8"');
    res.status(401).end();
  }
  (...)
Enter fullscreen mode Exit fullscreen mode

Suite à cette réponse, le navigateur demande à l'utilisateur de s'authentifier en faisant apparaitre une fenêtre privée dédiée. Les informations que fournit l'utilisateur servent à alimenter l'entête "authentication" qui n'était pas correct.
Si la propriété d'authentification est présente dans l'entête, il faut vérifier si les informations qu'elle contient sont correctes. Pour cela il faut extraire la partie credentials de la propriété "authorization", en en supprimant la partie Basic située au début, décoder le Base64 et séparer le nom d'utilisateur du mot de passe (séparés par :). On peut ensuite vérifier ces informations avec les valeurs attrendus (dans notre cas ces valeurs sont définies dans ./config/secret.js). Si elles sont conformes on autorise l'accès en "laissant passer" la requête qui est transmise au middleware suivant par un appel à next. Dans le cas contraire, une nouvelle demande d'authentification est envoyée, on procède alors exactement de la même manière que précédemment.

// fichier ./middleware/authentication.js
const secret = require ('../config/secret.js');

const authentication = (req, res, next) => {
  (...)
  else {
    /*extraire la partie credentials de la propriété "authorization", en en supprimant la partie Basic située au début, décoder le Base64 */
    const credentials = new Buffer.from(authheader.split(' ')[1],'base64').toString();
    const [user,password] = credentials.split(':');
    if (user === secret.user && password === secret.password) {
        // let's continue
        next()
    } else {
      // mauvais user name ou password
      res.setHeader('WWW-Authenticate', 'Basic realm="mon site à moi", charset="UTF-8"');
      res.status(401).end();
    }
  }
Enter fullscreen mode Exit fullscreen mode

En synthèse, voici le code complet de ce middleware :

// fichier ./middleware/authentifcation.js
const secret =require('../config/secret.js');

const authentification = (req, res, next) => {
    const authheader = req.headers.authorization;
    if (!authheader) {
        res.setHeader('WWW-Authenticate', 'Basic realm="mon site à moi", charset="UTF-8"');
        res.status(401).end();
    }
    else {
      /*extraire la partie credentials de la propriété "authorization", en en supprimant la partie Basic située au début, décoder le Base64 */
      const credentials  = new Buffer.from(authheader.split(' ')[1],'base64').toString();
      const [user,password] = credentials.split(':');
      if (user === secret.user && password === secret.password) {
          // let's continue
          next();
      } else {
        // mauvais user name ou password
        res.setHeader('WWW-Authenticate', 'Basic realm="mon site à moi", charset="UTF-8"');
        res.status(401).end();
      }
    }
}

module.exports = authentification;
Enter fullscreen mode Exit fullscreen mode

Dans, cet exemple, pour des raisons de simplicité, l'identifiant et le mot de passe sont fixés de manière unique dans le fichier /config/secret.js. On peut envisager qu'à chaque tentative d'authentification, dans le script /middleware/authentifcation.js, la fonction authentication vérifie si le couple (identifiant, mot de passe) reçu correspond à un utilisateur enregistré dans une base de données.

Exploitation du middleware d'authentification

Le middleware d'authentification doit maintenant être installé. Deux situations sont envisageables : soit on souhaite protéger l'ensemble du site, soit certaines pages seulement doivent être protégées.

Dans le premier cas, le middleware doit être installé globalement. Cela peut être réalisé au niveau du fichier ./app.js en positionnant ce middleware avant tous les autres, ce qui impose le contrôle d'accès avant tout autre traitement de la requête. Comme nous l'avons vu dans la définition de notre middleware, seule une authentification réussie permet d'accéder aux autres middlewares installés à la suite, grâce à l'appel à next.

// fichier ./app.js
(...)
const app = express();

const authentification = require('./middlewares/authentifcation.js');
app.use(authentication);
(...)
Enter fullscreen mode Exit fullscreen mode

Dans le second cas, on va préciser l'utilisation du middleware d'authentification pour chacun des chemins (ou routes) qui doit être contrôlé. On utilise pour cela une possibilité que nous n'avons pas encore explorée. Il s'agit du fait que la méthode use (que ce soit au niveau de l'application ou d'un routeur) peut accepter plusieurs fonctions middlewares qui sont examinées séquentiellement. Un appel à next au sein d'une telle fonction transmet la requête à la fonction suivante s'il y en a une (et au middleware suivant sinon).

Nous l'appiquons ici, au niveau du routeur ./routes/index.js pour mettre en place un contrôle d'accès sur les chemins "/protect" uniquement. Il suffit pour ces routes de préciser avant la fonction contrôleur qui gère leur logique (par facilité c'est la même fonction ici) que le middleware d'authentification doit être utilisé :

// fichier ./app.js
const express = require("express");
const indexRouter = require("./routes/index");
const protectRouter = require("./routes/protect");
const authentification = require("./middleware/authentifcation.js");
var app = express();

// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");

app.use("/", indexRouter);
app.use("/protect",authentification, protectRouter);
app.listen(9000,()=>{
  console.log("active au port 9000");
})
module.exports = app;

Enter fullscreen mode Exit fullscreen mode

On peut alors vérifier que l'on a accès sans contrôle a URL http://localhost:9000/ mais qu'il faut s'authentifier pour (http://localhost:9000/protect).

Pour ne pas avoir à fermer le navigateur, si l'on a démarré le serveur avec nodemon, il suffit de modifier les informations d'authentification dans ./config/secret.js pour déclencher à nouveau le processus d'authentification.

Top comments (0)

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

⭐️🎀 JavaScript Visualized: Promises & Async/Await

async await