Aller au contenu principal

37-4 Connexion et retour de jeton JWT

La dernière étape consiste à procéder à l'authentification.

Le principe est le suivant:

  1. Recevoir les informations de connexion via une requête HTTP
  2. Passer les informations à la couche service (ex.: AuthService)
  3. Récupérer l'utilisateur de la BD correspondant au courriel (si existe)
  4. Comparer la version hachée du mot de passe dans la BD avec la version hachée du mot de passe reçu
  5. Si le tout concorde, générer un jeton JWT
  6. Si le tout ne concorde pas, retourner une erreur d'authentification.

Créer l'action connexion du contrôleur auth

Dans le contrôleur d'authentification, on créera une action responsable de répondre via POST à /auth.

src/auth/auth.controller.ts
@ApiTags('auth')
@Controller('auth')
export class AuthController {
@Post()
async connexion(@Body() requeteConnexionDto: RequeteConnexionDto) {

}
}

Cela créera en effet un chemin associé au niveau de l'API.

Imgur

Créer l'action connexion associée au niveau du service

src/auth/auth.service.ts
@Injectable()
export class AuthService {
async connexion(requeteConnexionDto: RequeteConnexionDto) {

}
}

Ensuite, le contrôleur peut y faire appel.

export class AuthController {
constructor(private authService: AuthService) {}

@Post()
async connexion(@Body() requeteConnexionDto: RequeteConnexionDto) {
return this.authService.connexion(requeteConnexionDto);
}
}

Procéder à la connexion

  1. La première étape consiste à récupérer l'utilisateur de la base de données.
src/auth/auth.service.ts
async connexion(requeteConnexionDto: RequeteConnexionDto) {
const utilisateur = // OH NON, CE BOUT DE CODE EST MANQUANT!
// Vous devrez trouver l'utilisateur par son courriel.
// N'ubliez pas de convertir le courriel reçu en `lowercase` pour faire la recherche.
}
  1. Vérifier si un utilisateur a été retourné
src/auth/auth.service.ts
async connexion(requeteConnexionDto: RequeteConnexionDto) {
const utilisateur = // VOTRE CODE DE RÉCUPÉRATION

if(!utilisateur) {
throw new UnauthorizedException();
}
info

Bien qu'il est préférable d'utiliser le contrôleur pour retourner des codes d'erreur HTTP, comme nous avons quelques cas de figure à considérer ici, il demeure plus simple de les générer directement dans le service.

info

Il est déjà possible de faire un premier test Postman vers un utilisateur qui n'existe pas dans la BD. Par exemple.

POST http://localhost:3000/auth

{
"courriel": "NotFoundbenoit.tremblay@cegepdrummond.ca",
"password": "p@ssword"
}

Devrait retourner une erreur 401 Unauthorized.

  1. Comparer le mot de passe reçu avec celui dans la base de données. Pour vérifier le mot de passe de l'utilisateur, on peut comparer celui reçu avec celui dans la BD. Mais attention, il faut hasher le mot de passe reçu pour vérifier que la signature est la même que celle en BD. bcrypt offre la fonctionnalité compare pour cela.
src/auth/auth.service.ts
async connexion(requeteConnexionDto: RequeteConnexionDto) {
const utilisateur = // VOTRE CODE DE RÉCUPÉRATION

if(!utilisateur) {
throw new UnauthorizedException();
}

const passwordMatch = // UTILISER bcrypt.compare POUR COMPARER LES MOTS DE PASSE
if(!passwordMatch){
throw new UnauthorizedException();
}
}
info

La librairie bcrypt expose une fonction compare qui permet de passer en premier argument un mot de passe en texte clair et en deuxième argument un mot de passe haché.

La fonction compare s'occupera premièrement de hacher le mot de passe reçu en texte clair, puis de comparer le hash par la suite.

Cette action est possible puisque le mot de passe haché dans la base de données contient les informations suivantes: le salt utilisé, le coût de l'algorithme (itérations) et le mot de passe haché.

  1. Retourner un jeton JWT signé
src/auth/auth.service.ts
constructor(private jwtService: JwtService) {}

async connexion(requeteConnexionDto: RequeteConnexionDto) {
const utilisateur = // VOTRE CODE DE RÉCUPÉRATION

if(!utilisateur) {
throw new UnauthorizedException();
}

const passwordMatch = // VOTRE CODE
if(!passwordMatch){
throw new UnauthorizedException()
}

const access_token = this.jwtService.sign({
sub: utilisateur.id,
courriel: utilisateur.courriel,
nomUtilisateur: utilisateur.nomUtilisateur,
});
return { access_token };
}
info

jwtService.sign signe un jeton avec la clé secrète du fichier de configuration. Le jeton est au fonds un objet JSON, encodé par la suite en base64.

  • sub pour subject, soit l'identifiant de l'utilisateur
  • courriel: le courriel de l'utilisateur
  • nomUtilisateur: le nom d'utilisateur

Test Postman

Il est maintenant temps d'essayer une requête de connexion!

  • POST http://localhost:3000/auth
  • body (sera possiblement différent dans votre cas selon votre user/pw)
    {
    "courriel": "benoit.tremblay@cegepdrummond.ca",
    "password": "p@ssword"
    }

Si tout se passe bien, vous devriez recevoir un jeton!

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjMsImNvdXJyaWVsIjoiYmVub2l0LnRyZW1ibGF5QGNlZ2VwZHJ1bW1vbmQuY2EiLCJub21VdGlsaXNhdGV1ciI6ImJlbnRyZW1ibGF5IiwiaWF0IjoxNjk5NjI4NTMyLCJleHAiOjE2OTk2Mjg1MzJ9.aJ-t9Kpb8lKlAfcCR4E0kHfYzJv6Vpcggzvfkobalik"
}

Vous pouvez même utiliser le site jwt.io pour décoder le jeton et en voir le contenu:

Imgur