Express et JWT
JWT (JSON Web Token) permet d’authentifier un utilisateur lors de chaque appel d'un point d'interface logicielle.
- Le jeton est généré par le serveur, qui s’assure que l’utilisateur est bien celui qu’il prétend.
- Le jeton est envoyé au client et ce dernier le conserve dans un témoin pour l’utiliser à chaque appel d'un point d'interface logicielle.
- Un intergiciel du côté du serveur valide chaque appel d'un point d'interface logicielle en vérifiant le jeton.
- Si le jeton n’est pas valide, le point d'interface logicielle ne retourne pas de données.
Manuel
Installer le module pour créer et valider les jetons
Le service de génération de jetons
src/services/JetonService.ts
// **** Variables **** //
import { IUserLogin } from '@src/models/User';
import UserService from './UserService';
import jwt from 'jsonwebtoken';
export const UTILISATEUR_NOT_FOUND_ERR = 'Utilisateur non trouvé';
// **** Functions **** //
/**
* Générer un jeton pour un utilisateur
*
* @param {IUserLogin} utilisateur - L'utilisateur demandant le jeton
* @returns {Promise} - Le jeton signé
*/
async function generateToken(utilisateur: IUserLogin): Promise<string> {
const utilisateurBD = (await UserService.getAll()).filter(
(user) => user.email === utilisateur.email
)[0];
if (utilisateurBD && utilisateurBD.password === utilisateur.password) {
return jwt.sign(utilisateur.email, process.env.JWT_SECRET as string);
} else {
return '';
}
}
// **** Export default **** //
export default {
generateToken,
} as const;
Le chemin pour les jetons
src/common/Paths.ts
/**
* Express router paths go here.
*/
export default {
Base: '/api',
GenerateToken: {
Base: '/generatetoken',
Get: '/',
},
Users: {
Base: '/users',
Get: '/all',
Add: '/add',
Update: '/update',
Delete: '/delete/:id',
},
} as const;
La route
src/routes/JetonRoutes.ts
import JetonService from '@src/services/JetonService';
import User from '@src/models/User';
import { IReq, IRes } from './common/types';
import check from './common/check';
// **** Functions **** //
/**
* Générer un jeton.
*
* @param {IReq} req - La requête au serveur
* @param {IRes} res - La réponse du serveur
*/
async function generateToken(req: IReq, res: IRes) {
const userLogin = check.isValid(req.body, 'userlogin', User.isUserLogin);
const token = await JetonService.generateToken(userLogin);
return res.send({ token: token });
}
// **** Export default **** //
export default {
generateToken,
} as const;
Le Router
src/routes/index.ts
import { Router } from 'express';
import Paths from '../common/Paths';
import UserRoutes from './UserRoutes';
import JetonRoutes from './JetonRoutes';
// **** Variables **** //
const apiRouter = Router();
// ** Add UserRouter ** //
// Init router
const userRouter = Router();
// Get all users
userRouter.get(Paths.Users.Get, UserRoutes.getAll);
userRouter.post(Paths.Users.Add, UserRoutes.add);
userRouter.put(Paths.Users.Update, UserRoutes.update);
userRouter.delete(Paths.Users.Delete, UserRoutes.delete);
// Add UserRouter
apiRouter.use(Paths.Users.Base, userRouter);
// ** Add JetonRouter ** //
// Init Router
const tokenRouter = Router();
// Generate token
tokenRouter.post(Paths.GenerateToken.Get, JetonRoutes.generateToken);
// Add JetonRouter
apiRouter.use(Paths.GenerateToken.Base, tokenRouter);
// **** Export default **** //
export default apiRouter;
L'intergiciel pour valider les jetons
src/util/authenticateToken.ts
import jwt from 'jsonwebtoken';
import { Response, Request, NextFunction } from 'express';
import HttpStatusCodes from '@src/common/HttpStatusCodes';
/**
* Intergiciel pour authentifier le jeton de l'utilisateur
*
* @param {Request} req - La requête au serveur
* @param {Response} res - La réponse du serveur
* @param {NextFunction} next - La fonction a appeler pour continuer le processus.
*/
function authenticateToken(req: Request, res: Response, next: NextFunction) {
// Ne pas vérifier le token si l'url est celui de generatetoken
const lastPartOfUrl = req.url.split('/').at(-1);
if (lastPartOfUrl === 'generatetoken') {
next();
return;
}
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) return res.sendStatus(HttpStatusCodes.UNAUTHORIZED);
jwt.verify(token, process.env.JWT_SECRET as string, (err: any, user: any) => {
console.log(err);
if (err) return res.sendStatus(HttpStatusCodes.FORBIDDEN);
next();
});
}
export default authenticateToken;
Ajouter l'intergiciel au serveur
src/server.ts
/**
* Setup express server.
*/
import cookieParser from 'cookie-parser';
import morgan from 'morgan';
import path from 'path';
import helmet from 'helmet';
import express, { Request, Response, NextFunction } from 'express';
import logger from 'jet-logger';
import 'express-async-errors';
import BaseRouter from '@src/routes';
import Paths from '@src/common/Paths';
import EnvVars from '@src/common/EnvVars';
import HttpStatusCodes from '@src/common/HttpStatusCodes';
import { RouteError } from '@src/common/classes';
import { NodeEnvs } from '@src/common/misc';
import authenticateToken from './util/authenticateToken';
// **** Variables **** //
const app = express();
// **** Setup **** //
// Basic middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser(EnvVars.CookieProps.Secret));
// Pour authentifier le jeton de l'utilisateur
app.use(authenticateToken);
// Show routes called in console during development
if (EnvVars.NodeEnv === NodeEnvs.Dev.valueOf()) {
app.use(morgan('dev'));
}
// Security
if (EnvVars.NodeEnv === NodeEnvs.Production.valueOf()) {
app.use(helmet());
}
// Add APIs, must be after middleware
app.use(Paths.Base, BaseRouter);
// Add error handler
app.use(
(
err: Error,
_: Request,
res: Response,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
next: NextFunction
) => {
if (EnvVars.NodeEnv !== NodeEnvs.Test.valueOf()) {
logger.err(err, true);
}
let status = HttpStatusCodes.BAD_REQUEST;
if (err instanceof RouteError) {
status = err.status;
}
return res.status(status).json({ error: err.message });
}
);
// **** Front-End Content **** //
// Set views directory (html)
const viewsDir = path.join(__dirname, 'views');
app.set('views', viewsDir);
// Set static directory (js and css).
const staticDir = path.join(__dirname, 'public');
app.use(express.static(staticDir));
// Nav to users pg by default
app.get('/', (_: Request, res: Response) => {
return res.redirect('/users');
});
// Redirect to login if not logged in.
app.get('/users', (_: Request, res: Response) => {
return res.sendFile('users.html', { root: viewsDir });
});
// **** Export default **** //
export default app;
Configurer Postman pour utiliser les jetons
-
Créer une requête POST pour obtenir un jeton.
-
Dans Script, ajouter le code suivant pour conserver le jeton dans une variable d'environnement.
-
Créer une requête GET pour obtenir les données. Dans la section Auth, sélectionner Bearer Token et ajouter la variable d'environnement
jwt-token
. -
Exécuter la requête de génération avant celle du GET pour obtenir les données.