JSON Web Token (JWT) est un standard ouvert (RFC 7519) qui permet l’échange sécurisé de données entre parties sous forme d’un objet JSON. Chaque JWT est constitué de trois parties :
Un JWT ressemble à ceci :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Pour générer un token JWT, nous utilisons une clé secrète pour signer le token. Voici un exemple de code utilisant Node.js et le module jsonwebtoken
.
const jwt = require('jsonwebtoken');
function generateToken(user) {
const payload = {
id: user.id,
email: user.email,
};
const secret = process.env.JWT_SECRET;
const options = {
expiresIn: '1h', // Le token expire dans 1 heure
};
return jwt.sign(payload, secret, options);
}
jsonwebtoken
:
const jwt = require('jsonwebtoken');
Cette ligne importe le module jsonwebtoken
qui permet de créer et de vérifier des tokens JWT.
generateToken
:
function generateToken(user) {
Cette fonction prend un objet user
en paramètre et génère un token JWT basé sur cet utilisateur.
payload
:
const payload = {
id: user.id,
email: user.email,
};
Le payload
contient les informations que nous voulons inclure dans le token. Ici, il s’agit de l’ID et de l’email de l’utilisateur.
const secret = process.env.JWT_SECRET;
La clé secrète utilisée pour signer le token est récupérée depuis les variables d’environnement. Assurez-vous que cette clé est stockée de manière sécurisée.
const options = {
expiresIn: '1h', // Le token expire dans 1 heure
};
Ces options définissent la durée de vie du token. Ici, le token expire dans 1 heure.
return jwt.sign(payload, secret, options);
Cette ligne signe le payload
avec la clé secrète et les options spécifiées, puis retourne le token JWT.
const user = { id: '12345', email: 'user@example.com' };
const token = generateToken(user);
console.log('Generated JWT:', token);
Dans cet exemple, nous créons un objet utilisateur avec un id
et un email
, puis nous générons un token JWT pour cet utilisateur et l’affichons dans la console.
function generateToken(user) {
const payload = {
id: user.id,
email: user.email,
role: user.role, // Ajout du rôle de l'utilisateur
};
const secret = process.env.JWT_SECRET;
const options = {
expiresIn: '2h', // Le token expire dans 2 heures
};
return jwt.sign(payload, secret, options);
}
const user = { id: '67890', email: 'admin@example.com', role: 'admin' };
const token = generateToken(user);
console.log('Generated JWT with role:', token);
function generateToken(user) {
const payload = {
id: user.id,
email: user.email,
permissions: user.permissions, // Ajout des permissions de l'utilisateur
issuer: 'HappiHub', // Ajout de l'émetteur du token
};
const secret = process.env.JWT_SECRET;
const options = {
expiresIn: '3h', // Le token expire dans 3 heures
};
return jwt.sign(payload, secret, options);
}
const user = { id: '54321', email: 'user@example.com', permissions: ['read', 'write'] };
const token = generateToken(user);
console.log('Generated JWT with additional claims:', token);
function generateToken(user, expiresIn) {
const payload = {
id: user.id,
email: user.email,
};
const secret = process.env.JWT_SECRET;
const options = {
expiresIn: expiresIn || '1h', // Utilisation d'une durée de vie personnalisée ou par défaut à 1 heure
};
return jwt.sign(payload, secret, options);
}
const user = { id: '11223', email: 'user2@example.com' };
const token = generateToken(user, '6h');
console.log('Generated JWT with custom expiry:', token);
admin
, user
).['read', 'write']
).Voici un exemple de génération de token JWT avec des claims obligatoires, recommandés et optionnels :
function generateToken(user) {
const payload = {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
permissions: user.permissions,
iss: 'HappiHub',
sub: 'userAuthentication',
aud: 'happihubClient',
exp: Math.floor(Date.now() / 1000) + (60 * 60), // Expire dans 1 heure
iat: Math.floor(Date.now() / 1000),
jti: 'uniqueIdentifier123'
};
const secret = process.env.JWT_SECRET;
const options = {};
return jwt.sign(payload, secret, options);
}
const user = {
id: '67890',
email: 'admin@example.com',
name: 'John Doe',
role: 'admin',
permissions: ['read', 'write', 'delete']
};
const token = generateToken(user);
console.log('Generated JWT with various claims:', token);
Pour valider un token JWT, nous devons vérifier sa signature et s’assurer qu’il n’est pas expiré. Voici un exemple de code.
function validateToken(token) {
const secret = process.env.JWT_SECRET;
try {
const decoded = jwt.verify(token, secret);
return decoded;
} catch (err) {
console.error('Invalid token:', err);
return null;
}
}
validateToken
:
function validateToken(token) {
Cette fonction prend un token
en paramètre et retourne le contenu décodé du token s’il est valide.
const secret = process.env.JWT_SECRET;
La clé secrète utilisée pour vérifier la signature du token est récupérée depuis les variables d’environnement.
try...catch
pour la validation :
try {
const decoded = jwt.verify(token, secret);
return decoded;
} catch (err) {
console.error('Invalid token:', err);
return null;
}
try
: Tente de vérifier le token en utilisant la méthode jwt.verify
du module jsonwebtoken
. Si le token est valide, il retourne le contenu décodé du token.catch
: Si la vérification échoue (par exemple, si le token est invalide ou expiré), capture l’erreur, affiche un message d’erreur et retourne null
.const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Remplacer par un token réel
const decodedToken = validateToken(token);
if (decodedToken) {
console.log('Token is valid:', decodedToken);
} else {
console.log('Token is invalid');
}
Dans cet exemple, nous remplaçons token
par un token JWT réel, validons le token et affichons le contenu décodé s’il est valide, ou un message d’erreur s’il est invalide.
function validateTokenWithExpiration(token, maxAge) {
const secret = process.env.JWT_SECRET;
try {
const decoded = jwt.verify(token, secret);
const currentTime = Math.floor(Date.now() / 1000);
if (decoded.exp < currentTime || (decoded.iat + maxAge) < currentTime) {
console.error('Token is expired');
return null;
}
return decoded;
} catch (err) {
console.error('Invalid token:', err);
return null;
}
}
const maxAge = 3600; // 1 heure en secondes
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Remplacer par un token réel
const decodedToken = validateTokenWithExpiration(token, maxAge);
if (decodedToken) {
console.log('Token is valid with custom expiration:', decodedToken);
} else {
console.log('Token is invalid or expired');
}
function validateTokenWithRole(token, requiredRole) {
const secret = process.env.JWT_SECRET;
try {
const decoded = jwt.verify(token, secret);
if (decoded.role !== requiredRole) {
console.error('Invalid role');
return null;
}
return decoded;
} catch (err) {
console.error('Invalid token:', err);
return null;
}
}
const requiredRole = 'admin';
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Remplacer par un token réel
const decodedToken = validateTokenWithRole(token, requiredRole);
if (decodedToken) {
console.log('Token is valid with role:', decodedToken);
} else {
console.log('Token is invalid or role mismatch');
}
function validateTokenWithClaims(token, claims) {
const secret = process.env.JWT_SECRET;
try {
const decoded = jwt.verify(token, secret);
for (let key in claims) {
if (decoded[key] !== claims[key]) {
console.error(`Invalid claim: ${key}`);
return null;
}
}
return decoded;
} catch (err) {
console.error('Invalid token:', err);
return null;
}
}
const claims = { role: 'admin', permissions: ['read', 'write'] };
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Remplacer par un token réel
const decodedToken = validateTokenWithClaims(token, claims);
if (decodedToken) {
console.log('Token is valid with claims:', decodedToken);
} else {
console.log('Token is invalid or claims mismatch');
}
Les tokens JWT ont une durée de vie limitée pour des raisons de sécurité. Un token expiré ne peut plus être utilisé pour accéder aux ressources protégées. Il est donc nécessaire de rafraîchir le token avant qu’il n’expire pour maintenir la session de l’utilisateur active. Le rafraîchissement des tokens permet de prolonger la durée de vie de la session de l’utilisateur sans exiger une nouvelle authentification complète.
Voici comment rafraîchir un token avant qu’il n’expire.
function refreshToken(token) {
const decoded = validateToken(token);
if (decoded) {
return generateToken(decoded);
}
return null;
}
refreshToken
:
function refreshToken(token) {
Cette fonction prend un token
en paramètre et retourne un nouveau token JWT s’il est valide.
const decoded = validateToken(token);
La fonction validateToken
est utilisée pour vérifier la validité du token existant et retourner son contenu décodé. Si le token est invalide, decoded
sera null
.
if (decoded) {
return generateToken(decoded);
}
Si le token est valide, nous générons un nouveau token en utilisant les informations décodées du token existant.
null
si le token est invalide :
return null;
Si le token n’est pas valide, la fonction retourne null
.
const oldToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Remplacer par un token réel
const newToken = refreshToken(oldToken);
if (newToken) {
console.log('Token refreshed:', newToken);
} else {
console.log('Failed to refresh token');
}
Dans cet exemple, nous remplaçons oldToken
par un token JWT réel, tentons de le rafraîchir, et affichons le nouveau token s’il est rafraîchi avec succès, ou un message d’erreur s’il échoue.
function refreshTokenWithCheck(token) {
const decoded = validateToken(token);
if (decoded) {
const currentTime = Math.floor(Date.now() / 1000);
const timeLeft = decoded.exp - currentTime;
const minTimeLeft = 60 * 15; // Rafraîchir si le temps restant est inférieur à 15 minutes
if (timeLeft < minTimeLeft) {
return generateToken(decoded);
} else {
console.log('Token still has sufficient time left');
return token;
}
}
return null;
}
const oldToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Remplacer par un token réel
const newToken = refreshTokenWithCheck(oldToken);
if (newToken) {
console.log('Token refreshed or still valid:', newToken);
} else {
console.log('Failed to refresh token');
}
function refreshTokenWithUpdatedInfo(token, updatedUser) {
const decoded = validateToken(token);
if (decoded) {
// Mettre à jour les informations utilisateur dans le payload
decoded.email = updatedUser.email || decoded.email;
decoded.role = updatedUser.role || decoded.role;
return generateToken(decoded);
}
return null;
}
const oldToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Remplacer par un token réel
const updatedUser = { email: 'newemail@example.com', role: 'admin' };
const newToken = refreshTokenWithUpdatedInfo(oldToken, updatedUser);
if (newToken) {
console.log('Token refreshed with updated info:', newToken);
} else {
console.log('Failed to refresh token');
}
function refreshTokenWithNotification(token) {
const decoded = validateToken(token);
if (decoded) {
const newToken = generateToken(decoded);
if (newToken) {
console.log('Token has been refreshed for user:', decoded.email);
return newToken;
}
}
return null;
}
const oldToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Remplacer par un token réel
const newToken = refreshTokenWithNotification(oldToken);
if (newToken) {
console.log('Token refreshed:', newToken);
} else {
console.log('Failed to refresh token');
}
Les middlewares permettent de gérer l’authentification et l’autorisation de manière centralisée, simplifiant ainsi la logique de sécurité et améliorant la maintenabilité du code. En utilisant des middlewares, vous pouvez appliquer des règles d’authentification et d’autorisation à toutes les requêtes ou à certaines routes spécifiques de manière uniforme et efficace.
Ce middleware vérifie la présence d’un token JWT dans les en-têtes des requêtes et valide ce token.
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) return res.sendStatus(401); // Si aucun token, retournez 401
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403); // Si le token est invalide, retournez 403
req.user = user;
next(); // Passez au middleware ou route suivant
});
}
jsonwebtoken
:
const jwt = require('jsonwebtoken');
Cette ligne importe le module jsonwebtoken
qui permet de vérifier les tokens JWT.
authenticateToken
:
function authenticateToken(req, res, next) {
Cette fonction est un middleware qui prend les objets req
, res
et next
en paramètres.
Authorization
:
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
Cette partie du code vérifie la présence de l’en-tête Authorization
et extrait le token en supprimant le mot “Bearer” qui le précède.
if (token == null) return res.sendStatus(401); // Si aucun token, retournez 401
Si aucun token n’est présent, la requête est rejetée avec un statut HTTP 401 (Unauthorized).
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403); // Si le token est invalide, retournez 403
req.user = user;
next(); // Passez au middleware ou route suivant
});
La méthode jwt.verify
vérifie la validité du token en utilisant la clé secrète. Si le token est invalide, la requête est rejetée avec un statut HTTP 403 (Forbidden). Si le token est valide, l’objet user
décodé est attaché à l’objet req
et le contrôle est passé au middleware ou à la route suivante.
Appliquez le middleware à des routes spécifiques pour protéger ces routes.
const express = require('express');
const app = express();
app.get('/protected', authenticateToken, (req, res) => {
res.send('This is a protected route');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Dans cet exemple, nous utilisons Express.js pour créer un serveur et appliquons le middleware authenticateToken
à la route /protected
. Toute requête à cette route doit inclure un token JWT valide dans l’en-tête Authorization
.
Ce middleware vérifie non seulement la validité du token, mais également le rôle de l’utilisateur pour s’assurer qu’il a les permissions appropriées pour accéder à la route.
function authenticateTokenWithRole(role) {
return (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) return res.sendStatus(401); // Si aucun token, retournez 401
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403); // Si le token est invalide, retournez 403
if (user.role !== role) return res.sendStatus(403); // Si le rôle ne correspond pas, retournez 403
req.user = user;
next(); // Passez au middleware ou route suivant
});
};
}
app.get('/admin', authenticateTokenWithRole('admin'), (req, res) => {
res.send('This is an admin route');
});
Ce middleware enregistre chaque tentative de connexion pour des fins de journalisation et d’audit.
const fs = require('fs');
function authenticateTokenWithLogging(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) {
fs.appendFileSync('access.log', `Unauthorized access attempt on ${new Date().toISOString()}\n`);
return res.sendStatus(401); // Si aucun token, retournez 401
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
fs.appendFileSync('access.log', `Forbidden access attempt on ${new Date().toISOString()}: ${err.message}\n`);
return res.sendStatus(403); // Si le token est invalide, retournez 403
}
req.user = user;
next(); // Passez au middleware ou route suivant
});
}
app.get('/protected', authenticateTokenWithLogging, (req, res) => {
res.send('This is a protected route');
});
Ce middleware vérifie la validité du token et rafraîchit le token si sa durée de vie est proche de l’expiration.
function authenticateAndRefreshToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) return res.sendStatus(401); // Si aucun token, retournez 401
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403); // Si le token est invalide, retournez 403
const currentTime = Math.floor(Date.now() / 1000);
const tokenExpiry = user.exp;
// Rafraîchir le token si moins de 15 minutes restantes
if (tokenExpiry - currentTime < 900) {
const newToken = generateToken(user);
res.setHeader('Authorization', `Bearer ${newToken}`);
}
req.user = user;
next(); // Passez au middleware ou route suivant
});
}
app.get('/protected', authenticateAndRefreshToken, (req, res) => {
res.send('This is a protected route with token refresh');
});
Ne jamais stocker des tokens JWT dans le stockage local ou les cookies sans sécurisation. Utilisez httpOnly
et secure
pour les cookies.
httpOnly
: Empêche l’accès au cookie via JavaScript, réduisant ainsi les risques d’attaque par script intersite (XSS).secure
: Assure que le cookie est envoyé uniquement sur des connexions HTTPS.Exemple d’utilisation de cookies sécurisés en Express.js :
res.cookie('token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production', // Utilisez le flag secure en production
maxAge: 3600000 // 1 heure
});
Changez régulièrement les clés secrètes pour minimiser les risques en cas de fuite. La rotation des clés permet de sécuriser les tokens même si une clé secrète précédente a été compromise.
function generateToken(user) { const payload = { id: user.id, email: user.email }; return jwt.sign(payload, currentSecret, { expiresIn: ‘1h’ }); }
function validateToken(token) { for (let secret of secrets) { try { return jwt.verify(token, secret); } catch (err) { // Continue to the next secret } } return null; // Si aucun secret ne fonctionne, le token est invalide } ```
Invalider les tokens expirés et forcer les utilisateurs à se reconnecter. Implémenter une liste de révocation pour gérer les tokens invalidés.
function revokeToken(token) { revokedTokens.add(token); }
function isTokenRevoked(token) { return revokedTokens.has(token); }
function validateToken(token) { if (isTokenRevoked(token)) { return null; } try { return jwt.verify(token, currentSecret); } catch (err) { return null; } } ```
Voici des exemples pratiques de génération, validation et rafraîchissement de tokens JWT dans HappiHub.
const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
const secret = process.env.JWT_SECRET;
// Génération de Token
function generateToken(user) {
const payload = { id: user.id, email: user.email };
return jwt.sign(payload, secret, { expiresIn: '1h' });
}
// Validation de Token
function validateToken(token) {
try {
return jwt.verify(token, secret);
} catch (err) {
return null;
}
}
// Rafraîchissement de Token
function refreshToken(token) {
const decoded = validateToken(token);
return decoded ? generateToken(decoded) : null;
}
// Exemples d'utilisation
const user = { id: '12345', email: 'user@example.com' };
const token = generateToken(user);
console.log('Generated JWT:', token);
const decodedToken = validateToken(token);
if (decodedToken) {
console.log('Token is valid:', decodedToken);
} else {
console.log('Token is invalid');
}
const newToken = refreshToken(token);
if (newToken) {
console.log('Token refreshed:', newToken);
} else {
console.log('Failed to refresh token');
}
// Middleware d'Authentification
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) return res.sendStatus(401); // Si aucun token, retournez 401
jwt.verify(token, secret, (err, user) => {
if (err) return res.sendStatus(403); // Si le token est invalide, retournez 403
req.user = user;
next(); // Passez au middleware ou route suivant
});
}
// Exemple d'utilisation du middleware
app.get('/protected', authenticateToken, (req, res) => {
res.send('This is a protected route');
});
// Démarrer le serveur
app.listen(3000, () => {
console.log('Server is running on port 3000');
});