JsonWebToken
Structure
Les tokens d'authentification sont des jetons qui sont obtenus lors de la connexion d'un utilisateur. Ce dernier reçoit une clé cryptée qui contient des informations permettant de l'identifier. Ces jetons sont souvents stockés dans le LocalStorage
ou les Cookies
sur les navigateurs.
À chaque fois que l'utilisateur tentera de se rendre sur des ressources sécuriséesle serveur vérifiera la validé du token. Si le token est correct et correspond bien à l'utilisateur alors il autorisera l'accès à la ressource sinon il le rejettera (erreur 401 Unauthorized
).
Les tokens sont donc très pratique car ils évitent à un utilisateur de devoir se reconnecter systmétiquement à chaque requête au serveur. D'autant qu'on peut leur donner une durée de vie ce qui permettra d'éviter la reconnexion pendant ce laps de temps.
Pour utiliser les tokens avec nodeJS nous utilisons ce que l'on appelle les jsonwebtoken ou JWT. Il s'agit d'un format standard défini ici. Sa structure se décompose en 3 parties:
HEADER
: en-tête qui définit le type de token ainsi que son algorithme d'encryptage de signature. C'est un objet JSON.PAYLOAD
: qui possède les data que l'on souhaite stocker dans le JWT, comme l'id utilisateur, son rôle, (...). C'est un objet JSON.SIGNATURE
: Une signature numérique qui permet le chiffrement et le déchiffrement de notre JWT. On l'obtient en chiffrant le HEADER et le PAYLOAD avec l'encodage base64url. Ensuite, on les concatène en les séparant par un point. On obtient la signature de ce résultat avec l'algorithme choisi. Cette signature est ajoutée au résultat de la même manière (encodée et séparée par un point). Généralement on rajoute à cela une clé de chiffrement définie par nos soins. Le chiffrement est capital car il permet de vérifier l'intégrité du token.
Si vous définissez une clé de chiffrement dans votre projet veillez à bien la définir dans vos variables d'environnement pour que personne ne puisse y accéder depuis le cloud, cela serait catastrophique car on pourrait pirater votre application.
Créer un JWT
Pour utiliser ce standard nous utiliserons le package jsonwebtoken
. Le jsonwebtoken sera ensuite appelé dans l'endpoint de connexion (généralement l'endpoint /auth
) grâce à sa fonction sign
qui sert à chiffrer un nouveau token.
npm install jsonwebtoken
npm install -D @types/jsonwebtoken
Prenons l'exemple de la fonction qui réalise plusieurs opérations et finit par renvoyer l'utilisateur renseigné dans la requête. En plus des données de l'utilisateurs on va chercher à renvoyer dans la réponse un JWT.
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
export const login = async (req: Request, res: Response) => {
const { username, password } = req.body;
if (username !== 'admin' || password !== 'password') {
res.status(401).send('Identifiants invalides');
return;
}
const token = jwt.sign(
{ username }, // Payload
process.env.JWT_SECRET as jwt.Secret, // Secret
{ expiresIn: process.env.JWT_EXPIRES_IN } // Expiration
);
res.status(200).json({
token,
});
};
Les variables d'environnement
JWT_SECRET
etJWT_EXPIRES_IN
doivent être définies dans un fichier.env
à la racine de votre projet.
Vérifier un JWT
Votre réponse de connexion à renvoyé un JWT. Votre application ou vous-même allez désormais stocker ce JWT et l'utiliser dans vos prochaines requêtes afin de s'assurer que les endpoints nécessitant une connexion aient connaissance de votre token et vous délivre l'accès à leurs fonctions.
Afin qu'un endpoint cloisonnent ses accès uniquement à un utilisateur possédant un JWT valide nous allons créer un middleware d'authentification qui s'éxecutera avant les controlleurs.
Pour commencer il est de coutume d'envoyer le jwt dans l'entête de votre requête et plus spécifiquement dans la clé authorization
. On va donc récupérer le token contenu dans cette clé puis le vérifier à l'aide la fonction verify
qui prendra en paramètre le JWT et la clé de chiffrement secrète 'SECRET_TOKEN
.
import { NextFunction, Response, Request } from 'express';
import jwt from 'jsonwebtoken';
export const verifyJWT = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (authHeader) {
const token = authHeader.split(' ')[1]; // On récupère le token
const decodedToken = jwt.verify(
token,
process.env.JWT_SECRET as jwt.Secret
) as {
userId: string;
};
const userId = decodedToken.userId;
req.query = {
userId: userId,
};
next();
} else {
res.sendStatus(401); // Pas de token fourni
}
};
Point securité
- Ne pas stocker de JWT sensibles dans
localStorage
: Préférez les cookies sécurisés et HTTP-only. - Configurer une durée de vie appropriée : Les tokens doivent expirer rapidement (exp).
- Utiliser un algorithme de signature robuste : Préférer RS256 ou ES256 (asymétrique) à HS256.
- Vérifier toujours la signature et les claims : Assurez-vous que le token est valide et les informations dans le payload sont autorisées.