JWT (JSON Web Token): Complete Guide to Securing Your APIs and Applications
Security & API

JWT (JSON Web Token): Complete Guide to Securing Your APIs and Applications

How does JWT work? Best practices for securing your REST and GraphQL APIs. Tokens, signatures, refresh tokens: everything you need to know.

đŸ‡«đŸ‡· This article is currently available in French. English translation in progress.

Note: This article is available in French with full technical details and code examples.

Read the full French version →

JWT (JSON Web Token) est devenu le standard de facto pour l'authentification et l'autorisation dans les applications web modernes et les API RESTful. Compact, sécurisé et stateless, JWT permet de transmettre des informations de maniÚre fiable entre parties tout en garantissant leur intégrité. Ce guide explore en profondeur le fonctionnement de JWT, ses avantages, son implémentation pratique et les bonnes pratiques de sécurité indispensables.

Qu'est-ce que JWT (JSON Web Token) ?

JWT est un standard ouvert (RFC 7519) qui dĂ©finit une mĂ©thode compacte et autonome pour transmettre de maniĂšre sĂ©curisĂ©e des informations entre parties sous forme d'objet JSON. Ces informations peuvent ĂȘtre vĂ©rifiĂ©es et approuvĂ©es car elles sont signĂ©es numĂ©riquement.

Caractéristiques principales

  • Compact : taille rĂ©duite, facile Ă  transmettre via URL, header HTTP ou body
  • Autonome (self-contained) : contient toutes les informations nĂ©cessaires sur l'utilisateur, Ă©vitant des requĂȘtes supplĂ©mentaires en base de donnĂ©es
  • SignĂ© numĂ©riquement : garantit l'intĂ©gritĂ© et l'authenticitĂ© des donnĂ©es (signature HMAC ou RSA)
  • Stateless : le serveur n'a pas besoin de stocker les sessions, facilitant la scalabilitĂ©

Structure d'un JWT

Un JWT se compose de trois parties séparées par des points (.) : header.payload.signature

1. Header (En-tĂȘte)

Contient le type de token (JWT) et l'algorithme de signature utilisé (HS256, RS256, etc.).

{
  "alg": "HS256",
  "typ": "JWT"
}

Le header est encodé en Base64URL pour former la premiÚre partie du token.

2. Payload (Charge utile)

Contient les revendications (claims) : informations sur l'utilisateur et métadonnées. Il existe trois types de claims :

Claims enregistrés (Registered Claims)

  • iss (issuer) : Ă©metteur du token
  • sub (subject) : sujet du token (gĂ©nĂ©ralement l'ID utilisateur)
  • aud (audience) : destinataire du token
  • exp (expiration time) : date d'expiration (timestamp Unix)
  • nbf (not before) : date avant laquelle le token n'est pas valide
  • iat (issued at) : date d'Ă©mission
  • jti (JWT ID) : identifiant unique du token

Claims publics

DĂ©finis par ceux qui utilisent JWT. Doivent ĂȘtre enregistrĂ©s dans l'IANA JSON Web Token Registry ou dĂ©finis avec un namespace pour Ă©viter les collisions.

Claims privés

Claims custom définis entre les parties qui utilisent le token (email, role, permissions, etc.).

{
  "sub": "1234567890",
  "name": "John Doe",
  "email": "john.doe@example.com",
  "role": "admin",
  "iat": 1516239022,
  "exp": 1516242622
}

Le payload est encodé en Base64URL pour former la deuxiÚme partie du token.

3. Signature

Garantit que le token n'a pas été modifié. La signature est créée en combinant le header encodé, le payload encodé et une clé secrÚte (ou une paire de clés publique/privée pour RSA).

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

Token JWT complet

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Comment fonctionne JWT ?

Flux d'authentification typique

  1. Login : L'utilisateur envoie ses identifiants (email/password) au serveur
  2. Vérification : Le serveur vérifie les identifiants en base de données
  3. Génération JWT : Si les identifiants sont valides, le serveur génÚre un JWT contenant l'ID utilisateur, le rÎle, les permissions, etc.
  4. Retour du token : Le serveur retourne le JWT au client
  5. Stockage client : Le client stocke le JWT (localStorage, sessionStorage, cookie httpOnly)
  6. RequĂȘtes authentifiĂ©es : Pour chaque requĂȘte nĂ©cessitant une authentification, le client envoie le JWT dans le header Authorization: Bearer <token>
  7. Vérification serveur : Le serveur vérifie la signature du JWT et décode le payload pour identifier l'utilisateur
  8. Autorisation : Le serveur accorde ou refuse l'accĂšs en fonction des informations contenues dans le JWT (rĂŽle, permissions)

JWT vs Sessions : quelle différence ?

Authentification par session (traditionnelle)

  • Stateful : le serveur stocke les sessions en mĂ©moire, en base de donnĂ©es ou en cache (Redis)
  • Cookie de session : un identifiant de session est stockĂ© dans un cookie cĂŽtĂ© client
  • RequĂȘte base de donnĂ©es : Ă  chaque requĂȘte, le serveur doit interroger le store de sessions pour valider la session
  • ScalabilitĂ© limitĂ©e : complexitĂ© pour partager les sessions entre plusieurs serveurs (sticky sessions ou session store centralisĂ©)

Authentification par JWT

  • Stateless : le serveur ne stocke rien, toutes les informations sont dans le token
  • Token JWT : transmis via header Authorization ou cookie
  • Pas de requĂȘte base de donnĂ©es : le serveur vĂ©rifie uniquement la signature du token
  • ScalabilitĂ© optimale : n'importe quel serveur peut valider le token sans coordination

Tableau comparatif

CritĂšre Session JWT
Stockage serveur Oui (stateful) Non (stateless)
Scalabilité Complexe (sticky sessions) Simple (aucune coordination)
Performance RequĂȘte store sessions VĂ©rification signature uniquement
Révocation Immédiate (suppression session) Complexe (blacklist ou expiration courte)
Taille Cookie léger (ID session) Token plus volumineux (données + signature)
Multi-domaines Complexe (CORS, sous-domaines) Simple (header Authorization)

Implémentation JWT avec Node.js

Installation des dépendances

npm install jsonwebtoken bcryptjs express dotenv

Génération d'un JWT (login)

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

// Route de login
app.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body;

  // 1. Récupérer l'utilisateur en base de données
  const user = await User.findOne({ email });
  if (!user) {
    return res.status(401).json({ error: 'Identifiants invalides' });
  }

  // 2. Vérifier le mot de passe
  const isPasswordValid = await bcrypt.compare(password, user.password);
  if (!isPasswordValid) {
    return res.status(401).json({ error: 'Identifiants invalides' });
  }

  // 3. Générer le JWT
  const token = jwt.sign(
    {
      userId: user._id,
      email: user.email,
      role: user.role,
    },
    process.env.JWT_SECRET, // Clé secrÚte stockée en variable d'environnement
    { expiresIn: '24h' } // Expiration aprĂšs 24 heures
  );

  // 4. Retourner le token
  res.json({
    token,
    user: {
      id: user._id,
      email: user.email,
      name: user.name,
      role: user.role,
    },
  });
});

Middleware de vérification JWT

const jwt = require('jsonwebtoken');

const authMiddleware = (req, res, next) => {
  // 1. Récupérer le token depuis le header Authorization
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Token manquant' });
  }

  const token = authHeader.split(' ')[1];

  try {
    // 2. Vérifier et décoder le token
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    // 3. Attacher les informations utilisateur Ă  la requĂȘte
    req.user = decoded;
    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expiré' });
    }
    if (error.name === 'JsonWebTokenError') {
      return res.status(401).json({ error: 'Token invalide' });
    }
    return res.status(500).json({ error: 'Erreur serveur' });
  }
};

// Utilisation sur une route protégée
app.get('/api/profile', authMiddleware, (req, res) => {
  res.json({
    userId: req.user.userId,
    email: req.user.email,
    role: req.user.role,
  });
});

Refresh token pattern

Pour améliorer la sécurité, utiliser deux tokens :

  • Access token : courte durĂ©e de vie (15 min), utilisĂ© pour les requĂȘtes API
  • Refresh token : longue durĂ©e de vie (7 jours), stockĂ© en base de donnĂ©es, utilisĂ© pour renouveler l'access token
// Génération des deux tokens
const accessToken = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '7d' });

// Stocker le refresh token en base de données
await RefreshToken.create({
  userId: user._id,
  token: refreshToken,
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
});

// Route de refresh
app.post('/api/auth/refresh', async (req, res) => {
  const { refreshToken } = req.body;

  // Vérifier que le refresh token existe en base
  const storedToken = await RefreshToken.findOne({ token: refreshToken });
  if (!storedToken) {
    return res.status(401).json({ error: 'Refresh token invalide' });
  }

  try {
    // Vérifier la signature du refresh token
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);

    // Générer un nouvel access token
    const newAccessToken = jwt.sign(
      { userId: decoded.userId, email: decoded.email, role: decoded.role },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

    res.json({ accessToken: newAccessToken });
  } catch (error) {
    return res.status(401).json({ error: 'Refresh token expiré ou invalide' });
  }
});

Implémentation JWT cÎté client (React)

Stockage du token

// Option 1 : localStorage (simple mais vulnérable XSS)
localStorage.setItem('token', token);

// Option 2 : sessionStorage (supprimé à la fermeture du navigateur)
sessionStorage.setItem('token', token);

// Option 3 : cookie httpOnly (recommandé, non accessible en JavaScript)
// Le serveur définit le cookie avec l'attribut httpOnly
res.cookie('token', token, {
  httpOnly: true,
  secure: true, // HTTPS uniquement
  sameSite: 'strict',
  maxAge: 24 * 60 * 60 * 1000, // 24 heures
});

Envoi du token avec Axios

import axios from 'axios';

// Interceptor pour ajouter automatiquement le token Ă  chaque requĂȘte
axios.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Interceptor pour gérer l'expiration du token
axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      try {
        // Tenter de renouveler le token avec le refresh token
        const refreshToken = localStorage.getItem('refreshToken');
        const { data } = await axios.post('/api/auth/refresh', { refreshToken });

        localStorage.setItem('token', data.accessToken);
        originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;

        return axios(originalRequest);
      } catch (refreshError) {
        // Refresh token invalide ou expiré : rediriger vers login
        localStorage.removeItem('token');
        localStorage.removeItem('refreshToken');
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }

    return Promise.reject(error);
  }
);

Bonnes pratiques de sécurité JWT

1. Utiliser des secrets forts

La clĂ© secrĂšte JWT doit ĂȘtre longue (minimum 256 bits), alĂ©atoire et stockĂ©e en variable d'environnement. Ne jamais la commiter dans Git.

# .env
JWT_SECRET=a8f5d2c7b9e4f1a3d6c8b5e2a7f4d1c9b6e3a8f5d2c7b9e4f1a3d6c8b5e2a7f4

2. Définir une durée d'expiration courte

Access token : 15 minutes Ă  1 heure maximum. Utiliser un refresh token pour renouveler l'accĂšs sans redemander les identifiants.

3. Ne pas stocker d'informations sensibles dans le payload

Le payload JWT est seulement encodé en Base64, pas chiffré. N'y stockez jamais de mots de passe, numéros de carte bancaire ou données personnelles sensibles.

4. Utiliser HTTPS

Toujours transmettre les JWT sur des connexions HTTPS pour éviter l'interception par des attaquants (man-in-the-middle).

5. Implémenter une blacklist pour la révocation

Bien que JWT soit stateless, il est parfois nécessaire de révoquer un token (déconnexion, changement de mot de passe). Solutions :

  • Blacklist en cache : stocker les tokens rĂ©voquĂ©s en Redis avec expiration
  • Versionning : inclure une version dans le token et l'incrĂ©menter lors d'une rĂ©vocation
  • Expiration courte + refresh token : faciliter la rĂ©vocation en invalidant le refresh token en base

6. Protéger contre les attaques XSS

Si le token est stocké en localStorage/sessionStorage, il est vulnérable aux attaques XSS. Privilégier le stockage en cookie httpOnly (non accessible en JavaScript).

7. Protéger contre les attaques CSRF

Si le token est stocké en cookie, implémenter une protection CSRF (token CSRF, attribut SameSite=Strict).

8. Valider le token Ă  chaque requĂȘte

Ne jamais faire confiance au client. Toujours vérifier la signature du JWT cÎté serveur avant d'autoriser l'accÚs.

9. Utiliser l'algorithme RSA pour les architectures distribuées

Pour les microservices, privilégier RSA (RS256) : le serveur d'authentification signe avec la clé privée, les autres services vérifient avec la clé publique (pas besoin de partager un secret).

Cas d'usage de JWT

1. Authentification API RESTful

JWT est idĂ©al pour sĂ©curiser les API stateless : le client envoie le token Ă  chaque requĂȘte, le serveur vĂ©rifie et autorise l'accĂšs.

2. Single Sign-On (SSO)

JWT permet l'authentification unique sur plusieurs domaines : un serveur d'authentification central émet un JWT accepté par tous les services.

3. Échange d'informations sĂ©curisĂ©

JWT garantit l'intégrité des données : si un tiers modifie le payload, la signature ne correspondra plus et le token sera rejeté.

4. Autorisation fine (RBAC, ABAC)

Le JWT peut contenir les rĂŽles et permissions de l'utilisateur, permettant une autorisation cĂŽtĂ© serveur sans requĂȘte base de donnĂ©es.

{
  "userId": "123",
  "role": "admin",
  "permissions": ["users:read", "users:write", "users:delete"]
}

5. Authentification mobile et IoT

Les applications mobiles et objets connectĂ©s utilisent JWT pour Ă©viter de retransmettre les identifiants Ă  chaque requĂȘte.

Alternatives et compléments à JWT

OAuth 2.0

Protocole d'autorisation permettant à une application tierce d'accéder aux ressources d'un utilisateur sans connaßtre son mot de passe. JWT est souvent utilisé comme format de token dans OAuth 2.0 (access token, ID token).

OpenID Connect (OIDC)

Couche d'identité au-dessus d'OAuth 2.0, utilisant JWT pour l'ID token qui contient les informations d'identité de l'utilisateur.

PASETO (Platform-Agnostic Security Tokens)

Alternative à JWT conçue pour éviter certaines vulnérabilités (choix d'algorithme faible, algorithme "none"). PASETO impose des algorithmes sécurisés par défaut.

Macaroons

Tokens de capacité (capability tokens) permettant une délégation fine des permissions. Plus complexes que JWT mais offrent plus de flexibilité pour l'atténuation des droits.

Erreurs courantes à éviter

1. Utiliser l'algorithme "none"

Certaines bibliothÚques acceptent un JWT sans signature (alg: "none"). Toujours valider l'algorithme attendu cÎté serveur.

2. Stocker des données sensibles dans le payload

Le payload est visible par quiconque possÚde le token (décodage Base64). Ne jamais y mettre de mots de passe ou données confidentielles.

3. Ne pas définir d'expiration

Un JWT sans expiration (claim exp) est valide indéfiniment. Toujours définir une durée de vie.

4. RĂ©utiliser la mĂȘme clĂ© secrĂšte partout

Utiliser des clés différentes pour l'access token et le refresh token. En cas de compromission d'une clé, l'autre reste sécurisée.

5. Ne pas valider les claims

Toujours vérifier iss (émetteur), aud (audience), exp (expiration) pour s'assurer que le token est destiné à votre application.

Pourquoi choisir VOID pour votre authentification JWT ?

Expertise sécurité

Nous implémentons JWT selon les meilleures pratiques de sécurité : algorithmes robustes (HS256, RS256), expiration courte, refresh tokens, protection XSS/CSRF.

Architecture scalable

Nos architectures stateless JWT permettent de scaler horizontalement sans complexité : n'importe quel serveur peut valider un token sans coordination.

Intégrations OAuth/OIDC

Nous intégrons JWT avec OAuth 2.0 et OpenID Connect pour l'authentification via Google, Facebook, Azure AD, permettant le SSO et l'authentification déléguée.

Monitoring et audit

Nous instrumentons les flux d'authentification : logs centralisés, alertes sur tentatives de connexion suspectes, dashboards de sécurité temps réel.

Conformité réglementaire

Nos implémentations JWT respectent les standards de sécurité (OWASP, ISO 27001) et les réglementations sectorielles (PCI-DSS pour le paiement, loi 09-08 pour les données personnelles au Maroc).

Conclusion

JWT s'est imposé comme le standard de l'authentification moderne grùce à sa simplicité, sa scalabilité et sa sécurité. Bien implémenté, JWT offre une solution élégante pour sécuriser vos API et applications tout en facilitant l'expérience utilisateur. Chez VOID, nous maßtrisons l'intégralité de la chaßne : de l'architecture sécurisée à l'implémentation backend/frontend, du monitoring à la conformité réglementaire. Que vous construisiez une API RESTful, une architecture microservices ou une application mobile, nous concevons des systÚmes d'authentification robustes, performants et évolutifs basés sur JWT.

Tags:

JWTSécuritéAPIAuthentificationNode.js

Need Expert Support?

VOID accompanies you in your digital transformation projects in Morocco and Africa.