Guide des mécanismes d’authentification dans les applications Web modernes

Ce document présente les mécanismes d’authentification utilisés dans les applications web modernes, leurs avantages, inconvénients, domaines d’usage, ainsi que les bonnes pratiques de sécurité issues des recommandations de l’OWASP et des standards actuels.


1. Qu’est-ce que l’OWASP ?

Présentation

OWASP (Open Worldwide Application Security Project) est une organisation internationale à but non lucratif dédiée à l’amélioration de la sécurité des applications web et logicielles.

Elle fournit :

  • des guides de bonnes pratiques,
  • des standards de sécurité,
  • des outils open source,
  • des référentiels reconnus mondialement.

L’OWASP est indépendante des éditeurs et des technologies, ce qui rend ses recommandations largement adoptées dans l’industrie.

OWASP Top 10

Le document le plus connu est le OWASP Top 10, qui recense les dix catégories de vulnérabilités les plus critiques. Plusieurs concernent directement l’authentification et la gestion des identités, notamment :

  • Authentification défaillante,
  • Mauvaise gestion des sessions,
  • Contrôles d’accès cassés,
  • Exposition de données sensibles.

Principe fondamental OWASP

Le client (navigateur, SPA, application mobile) ne doit jamais être considéré comme fiable.

Toute logique de sécurité doit être implémentée et validée côté serveur.


2. Concepts fondamentaux

Authentification vs autorisation

  • L’authentification consiste à vérifier l’identité d’un utilisateur ou d’un système.
  • L’autorisation consiste à vérifier ce que cet utilisateur ou système est autorisé à faire.

Une application sécurisée doit traiter ces deux notions séparément.


3. Authentification par session et cookies (Web traditionnel)

Principe

Après la saisie d’un identifiant et d’un mot de passe, le serveur crée une session et transmet un identifiant de session au navigateur via un cookie HTTP.
Le navigateur renvoie automatiquement ce cookie à chaque requête suivante.

Avantages

  • Mécanisme simple et éprouvé,
  • Excellente intégration avec les navigateurs,
  • Contrôle total côté serveur,
  • Facile à mettre en œuvre dans les applications web traditionnelles.

Inconvénients

  • Peu adapté aux API REST,
  • Scalabilité plus complexe en environnement distribué,
  • Fortement couplé au navigateur.

Sécurité (OWASP)

  • Protection contre les attaques CSRF obligatoire,
  • Cookies configurés avec HttpOnly, Secure et SameSite,
  • Invalidation systématique des sessions à la déconnexion.

4. Authentification HTTP Basic

Principe

Les identifiants sont envoyés à chaque requête HTTP via l’en-tête Authorization, encodés en Base64.

Avantages

  • Très simple à implémenter,
  • Supporté nativement par HTTP,
  • Utile pour des tests ou des usages temporaires.

Inconvénients

  • Aucun mécanisme de session,
  • Le mot de passe est transmis à chaque requête,
  • Sécurité entièrement dépendante de HTTPS.

Recommandation

Cette méthode est déconseillée en production pour des utilisateurs finaux.


5. JWT (JSON Web Token)

Principe

Le serveur délivre un token signé contenant des informations appelées claims.
Le token est auto-contenu et peut être vérifié sans accès à une base de données.

Avantages

  • Stateless,
  • Très scalable,
  • Compatible avec SPA, applications mobiles et microservices,
  • Standard largement adopté.

Inconvénients

  • Révocation complexe,
  • Risques importants en cas de mauvaise implémentation,
  • Sécurité fortement dépendante du mode de stockage côté client.

Recommandations OWASP

  • Ne jamais stocker un JWT dans localStorage,
  • Préférer les cookies HttpOnly côté navigateur,
  • Utiliser des tokens de courte durée,
  • Mettre en place des refresh tokens,
  • Ne jamais stocker de données sensibles dans le token.

6. Authentification par token opaque

Principe

Le serveur génère un token aléatoire sans signification intrinsèque.
Ce token est stocké côté serveur et vérifié à chaque requête.

Avantages

  • Révocation simple,
  • Aucune donnée exposée côté client,
  • Plus sûr que les JWT dans certains contextes.

Inconvénients

  • Vérification serveur nécessaire à chaque requête,
  • Moins performant que les JWT à très grande échelle.

7. Cas d’usage : authentification dans une SPA (frontend)

Problématique

Une application frontend (React, Vue, Angular, etc.) ne peut pas protéger un secret.
Tout code exécuté côté client est potentiellement accessible ou modifiable.

Bonnes pratiques

  • Authentification réalisée via une API backend,
  • Stockage du token :
    • Cookie HttpOnly (recommandé),
    • Mémoire volatile (state JavaScript),
  • Protection contre les attaques XSS, CSRF et le rejeu de tokens.

Reco

CommonJS vs ECMAScript (ESM)

📘 Comprendre les fichiers .mjs dans Node.js

🧩 1. .mjs, c’est quoi ?

Un fichier .mjs est un module ECMAScript — c’est-à-dire un fichier JavaScript utilisant la syntaxe moderne des imports et exports, comme celle des navigateurs.

// fichier.mjs
import express from "express";
import { maFonction } from "./utils.mjs";

export const app = express();

👉 L’extension .mjs indique à Node.js que le fichier doit être interprété en mode ESM (ECMAScript Module)
et non en CommonJS (le système historique de Node).


⚙️ 2. Contexte historique

Avant 2020, Node.js utilisait uniquement le système CommonJS :

const express = require("express");
module.exports = app;

Mais depuis Node.js 14+, il supporte aussi les modules ECMAScript (mêmes modules que les navigateurs).

Pour éviter toute ambiguïté, Node a introduit une distinction stricte entre :

  • .js (fichiers interprétés comme CommonJS par défaut)
  • .mjs (fichiers interprétés comme modules ECMAScript)

⚙️ 3. Les deux systèmes de modules

Aspect CommonJS (.js) ESM (.mjs ou type: "module")
Importation const X = require("x") import X from "x"
Exportation module.exports = X export default X
Chargement Synchrone Asynchrone
Scope Global par fichier Strict (par module)
Compatibilité historique ✅ Node.js classique ✅ Node 14+
Syntaxe moderne ❌ Non ✅ Oui

🧠 4. Exemple comparatif

🟠 CommonJS (.js)

// utils.js
const sum = (a, b) => a + b;
module.exports = { sum };

// app.js
const { sum } = require("./utils.js");
console.log(sum(2, 3));

🔵 ECMAScript (.mjs)

// utils.mjs
export function sum(a, b) {
  return a + b;
}

// app.mjs
import { sum } from "./utils.mjs";
console.log(sum(2, 3));

🧩 5. Et si je veux rester en .js ?

Tu peux utiliser la syntaxe import/export sans passer en .mjs
en ajoutant ce paramètre dans ton package.json :

{
  "type": "module"
}

➡️ Dans ce cas, tous les fichiers .js du projet sont interprétés comme des modules ESM.
Tu n’as donc plus besoin de .mjs.

⚠️ Mais attention :

  • Tu ne pourras plus utiliser require() ni module.exports directement.
  • Tu devras tout passer en import/export.

🧱 6. Quand utiliser .mjs plutôt que .js ?

Situation Recommandation
Petit projet Node classique (Express, Mongo, etc.) ✅ Reste en .js (CommonJS)
Projet moderne, TypeScript, ou compatible navigateur ✅ Utilise .mjs ou "type": "module"
Tu veux mélanger les deux systèmes ⚠️ Possible mais à éviter
Librairie NPM moderne à publier .mjs recommandé pour compatibilité ESM

🧩 7. Mélanger CommonJS et ESM (cas avancé)

Il est possible de mélanger les deux, mais c’est souvent source de confusion.

Exemple :

  • main.mjs (ESM) peut importer du CommonJS :
    import pkg from "./legacy.js";
  • legacy.js (CommonJS) peut importer un ESM avec import() dynamique :
    const module = await import("./modern.mjs");

Mais cela complique la maintenance : le mieux est de choisir un seul style.


✅ 8. En résumé

Élément .js (CommonJS) .mjs (ESM)
Import require() import
Export module.exports export
Système Ancien (Node) Standard ECMAScript
Compatibilité Universelle Moderne
Recommandé en 2025 ⚙️ pour rétrocompatibilité 🟢 pour les projets modernes

💡 En clair :

  • .js = modules CommonJS (require, module.exports)
  • .mjs = modules ECMAScript (import, export)
  • Tu peux aussi garder .js et déclarer "type": "module" dans ton package.json si tu veux la syntaxe moderne partout.

404

Not Found

Guide complet — Gestion des Tokens sous Express.js

1. Introduction aux Tokens

Dans Express.js, un token est une preuve d’identité permettant d’autoriser un utilisateur ou un service à accéder à des ressources protégées.

Le serveur émet un token après authentification, puis le client l’envoie à chaque requête (souvent dans le header Authorization).

Les tokens remplacent les sessions classiques (cookies de session) dans les architectures modernes d’API REST ou microservices.


2. Les différentes approches modernes

A. JWT (JSON Web Token)

Un JWT est un jeton auto-contenu, signé par le serveur, qui contient des informations encodées (sub, email, role, etc.).

Bibliothèques modernes :

Structure d’un JWT :

header.payload.signature

Avantages :

  • Pas de stockage côté serveur.
  • Vérification rapide (signature HMAC ou RSA).
  • Standard international.

Inconvénients :

  • Difficile à révoquer.
  • Si compromis, le token reste valide jusqu’à expiration.
  • Vulnérable si stocké dans localStorage.

B. JWT + Refresh Token

Cette approche utilise deux jetons :

  • Access Token (courte durée, ex : 15 minutes)
  • Refresh Token (longue durée, ex : 7 jours)

L’Access Token est stocké en mémoire, le Refresh Token en cookie HttpOnly + Secure.

Cela permet un renouvellement transparent de session sans redemander le mot de passe.


Un JWT peut être stocké directement dans un cookie HttpOnly et Secure, qui remplace la session traditionnelle.

  • L’auth se fait automatiquement avec le cookie.
  • Le serveur vérifie le JWT sans base de données.
  • Idéal pour des applications full web (SSR, Next.js, etc.)

D. OIDC / OAuth2

OpenID Connect (OIDC) et OAuth2 sont utilisés pour :

  • L’authentification via tiers (Google, Microsoft, GitHub…)
  • Le Single Sign-On (SSO)
  • L’accès interservices sécurisé

Libs utiles :


E. Token Opaque + Redis

Le token opaque est un identifiant aléatoire sans signification :

3baf4d90-e95c-46f7-bb1f-3d27a36c6e52

Il ne contient aucune donnée utilisateur.
Le serveur stocke l’association dans Redis :

SET token:3baf4d90 { "userId": 42, "role": "admin" } EX 900

À chaque requête, le serveur consulte Redis pour vérifier la validité du token.

Avantages :

  • Révocation immédiate (supprimer la clé Redis)
  • Aucune donnée exposée au client
  • Contrôle total côté serveur

Inconvénients :

  • Redis devient un point central (stateful)
  • Nécessite un accès réseau rapide et fiable

3. Comparaison entre JWT et Token Opaque

Critère JWT Token Opaque + Redis
Structure Auto-contenu Identifiant aléatoire
Stockage Côté client Côté serveur
Vérification Locale (signature) Consultation Redis
Révocation Complexe Instantanée
Sécurité Bonne Très élevée
Scalabilité Excellente Bonne mais centralisée
Débogage Facile (lisible) Requiert Redis
Usage typique API publiques, microservices API internes, sécurité stricte

4. Concept de Stateless vs Stateful

A. Qu’est-ce qu’une API Stateless

Une API stateless ne garde aucun état entre les requêtes. Chaque requête contient toutes les informations nécessaires à son traitement.

Exemple : Un JWT est stateless : il contient toutes les infos (id, rôle, expiration).
Aucun appel externe n’est nécessaire.

B. Qu’est-ce qu’une API Stateful

Une API stateful conserve l’état côté serveur (ex : Redis, sessions).
Les requêtes dépendent d’informations mémorisées sur le serveur.

Exemple : Un token opaque nécessite de consulter Redis pour savoir à quel utilisateur il correspond.

C. Impact dans une architecture microservices

  • JWT (stateless) : chaque microservice peut vérifier les tokens seul.
  • Redis (stateful) : chaque service dépend du store commun.

Conséquence :

  • Le modèle stateful ajoute une dépendance centrale (Redis), réduisant l’autonomie et la résilience.
  • En revanche, il permet la révocation immédiate et un contrôle total.

5. Redis dans la gestion des Tokens

A. Qu’est-ce que Redis

Redis est une base clé/valeur en mémoire ultra-rapide, utilisée pour :

  • le cache,
  • les sessions,
  • les files d’attente,
  • les compteurs,
  • la communication temps réel.

Redis stocke les données directement dans la RAM, ce qui le rend des dizaines de fois plus rapide que les bases de données classiques.


B. Cas d’usage concrets avec Redis

  1. Cache ultra-rapide
  2. Gestion des sessions et tokens
  3. File d’attente (queue)
  4. Rate limiting
  5. Pub/Sub
  6. Stockage temporaire
  7. Compteurs et statistiques

C. Pourquoi Redis est si rapide

  • Données en RAM
  • Opérations atomiques
  • Protocole réseau minimaliste
  • Mono-thread
  • Optimisé pour la latence microseconde

D. Intégration Redis avec Express.js

npm install redis

Exemple simple :

const { createClient } = require("redis");
const client = createClient();

client.connect();

app.get("/", async (req, res) => {
  const count = await client.incr("visits");
  res.send(`Visites : ${count}`);
});

6. Bonnes pratiques de sécurité 2025

Risque Mesure recommandée
Token volé Durée courte + refresh token
Vol cookie HttpOnly + Secure + SameSite=Strict
Secret exposé Stocker la clé dans .env
Token forgé Signature HMAC ou RSA
Révocation Gérer une denylist ou Redis
Environnement HTTP Toujours utiliser HTTPS
Vérification Toujours vérifier signature et expiration

7. Exemples de code Express.js

JWT avec JOSE

import { SignJWT, jwtVerify } from "jose";

const secret = new TextEncoder().encode(process.env.JWT_SECRET);

export async function signToken(payload, expiresIn = "15m") {
  return await new SignJWT(payload)
    .setProtectedHeader({ alg: "HS256" })
    .setIssuedAt()
    .setExpirationTime(expiresIn)
    .sign(secret);
}

export async function verifyToken(token) {
  const { payload } = await jwtVerify(token, secret);
  return payload;
}

router.post("/login", async (req, res) => {
  const accessToken = await signToken({ sub: user.id }, "15m");
  const refreshToken = await signToken({ sub: user.id, type: "refresh" }, "7d");

  res.cookie("refresh_token", refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: "Strict",
    maxAge: 7 * 24 * 60 * 60 * 1000,
  });

  res.json({ accessToken });
});

Token Opaque + Redis

const { v4: uuidv4 } = require("uuid");
const redis = require("../utils/redis");

exports.login = (req, res) => {
  User.findOne({ email: req.body.email })
    .then((user) => {
      if (!user) return res.status(401).json({ error: "Utilisateur introuvable" });
      const token = uuidv4();
      redis.set(token, JSON.stringify({ id: user._id }), { EX: 900 });
      res.json({ token });
    })
    .catch((err) => res.status(500).json({ error: "Erreur serveur" }));
};

Fin du document.