Aller au contenu

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.

Installer le module pour créer et valider les jetons

console
npm install jsonwebtoken @types/jsonwebtoken

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 {IUtilisateur} 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';
/**
 * 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(401);

  jwt.verify(token, process.env.JWT_SECRET as string, (err: any, user: any) => {
    console.log(err);

    if (err) return res.sendStatus(403);

    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

  1. Créer une requête POST pour obtenir un jeton. Postman - Créer une requête POST pour obtenir un jeton

  2. Dans Script, ajouter le code suivant pour conserver le jeton dans une variable d'environnement.

    pm.environment.set("jwt-token", pm.response.json().token);
    
    Postman - Ajouter le code pour conserver le jeton dans une variable d'environnement

  3. 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. Postman - Ajouter le jeton à la requête GET

  4. Exécuter la requête de génération avant celle du GET pour obtenir les données.