Introduction
C'est une réalité incontestable que l'authentification est essentielle dans toute application ou système si vous souhaitez sécuriser les données des utilisateurs et permettre un accès sécurisé aux informations. L'authentification est la procédure permettant d'établir ou de démontrer que quelque chose est vrai, légitime ou valide.
Prérequis
Ce tutoriel est une démonstration pratique. Pour suivre, assurez-vous d'avoir les éléments suivants en place :
- Node.js s'exécute sur votre système, car NestJS est un framework Node.js
- MongoDB installé
Qu'est-ce que NestJS ?
Nest (NestJS) est un cadre d'application côté serveur Node.js permettant de créer des applications évolutives et efficaces.
Il est écrit en TypeScript et construit sur Express, un cadre très minimaliste qui est excellent en soi mais qui manque de structure. Il combine des paradigmes de programmation tels que la programmation orientée objet, la programmation fonctionnelle et la programmation réactive fonctionnelle.
C'est un framework à utiliser si vous voulez beaucoup de structure sur votre backend. Sa syntaxe et sa structure sont très similaires à AngularJS, un framework frontal. Et il utilise TypeScript, les services et l'injection de dépendances de la même manière qu'AngularJS.
Il utilise des modules et des contrôleurs, et vous pouvez créer des contrôleurs pour un fichier à l'aide de l'interface de ligne de commande.
Les modules NestJS vous permettent de regrouper les contrôleurs et les fournisseurs de services associés dans un seul fichier de code. En termes simples, un module NestJS est un fichier TypeScript avec le @Module annotation (). Ce décorateur informe le framework NestJS des contrôleurs, fournisseurs de services et autres ressources associées qui seront instanciés et utilisés ultérieurement par le code de l'application.
Qu'est-ce que l'authentification basée sur la session ?
L'authentification basée sur la session est une méthode d'authentification de l'utilisateur dans laquelle le serveur crée une session après une connexion réussie, avec l'ID de session stocké dans un cookie ou un stockage local dans votre navigateur.
Lors de demandes ultérieures, votre cookie est validé par rapport à l'ID de session stocké sur le serveur. S'il y a une correspondance, la demande est considérée comme valide et traitée.
Lorsque vous utilisez cette méthode d'authentification, il est essentiel de garder à l'esprit les meilleures pratiques de sécurité suivantes :
- Générer des identifiants de session longs et aléatoires (128 bits est la longueur recommandée) pour rendre les attaques par force brute inefficaces
- Évitez de stocker des données sensibles ou spécifiques à l'utilisateur
- Rendre les communications HTTPS obligatoires pour toutes les applications basées sur des sessions
- Créer des cookies dotés d'attributs sécurisés et HTTP uniquement
Pourquoi l'authentification basée sur la session ?
L'authentification basée sur la session est plus sécurisée que la plupart des méthodes d'authentification car elle est simple, sécurisée et a une taille de stockage limitée. Il est également considéré comme la meilleure option pour les sites Web du même domaine racine.
Configuration du projet
Commencez la configuration de votre projet en installant Nest CLI globalement. Vous n'avez pas besoin de le faire si NestJS CLI est déjà installé.
La CLI Nest est un outil d'interface de ligne de commande permettant de configurer, de développer et de gérer les applications Nest.
npm i -g @nestjs/cli
Maintenant, configurons votre projet en exécutant la commande suivante :
nest new session-based-auth
La commande ci-dessus crée une application Nest avec certains passe-partout, puis vous invite à choisir votre gestionnaire de packages préféré pour installer les modules requis pour exécuter votre application. Pour la démonstration, ce tutoriel utilise npm . Appuyez sur la touche Entrée pour continuer avec npm .
Si tout s'est bien passé, vous devriez voir une sortie comme celle de la capture d'écran ci-dessous sur votre terminal.
Une fois l'installation terminée, déplacez-vous dans le répertoire de votre projet et exécutez l'application avec la commande ci-dessous :
npm run start:dev
La commande ci-dessus exécute l'application et surveille les modifications. Votre projet src
la structure des dossiers doit ressembler à ceci.
└───src
│ └───app.controller.ts
│ └───app.modules.ts
│ └───app.service.ts
│ └───main.ts
Installer les dépendances
Maintenant que votre application est configurée, installons les dépendances nécessaires.
npm install --save @nestjs/passport passport passport-local
La commande ci-dessus installe Passport.js, une bibliothèque d'authentification nest.js populaire.
Installez également les types pour la stratégie avec la commande ci-dessous :
Il contient des définitions de type pour passport-local
.
npm install --save-dev @types/passport-local
Configurer la base de données MongoDB dans NestJS
Pour configurer et connecter votre base de données, installez le package Mongoose et le wrapper NestJS avec la commande suivante :
npm install --save @nestjs/mongoose mongoose
Le wrapper Mongoose NestJS vous aide à utiliser Mongoose dans l'application NestJS et offre une prise en charge TypeScript approuvée.
Maintenant, rendez-vous sur votre app.module.ts
, et importez la mongoose
module de @nestjs/mongoose
. Appelez ensuite le forRoot()
method, une méthode fournie par le module Mongoose, et transmettez la chaîne d'URL de votre base de données.
Configuration de votre connexion à la base de données dans app.module.ts
aide votre application à se connecter à la base de données immédiatement au démarrage du serveur - après avoir exécuté votre application puisqu'il s'agit du premier module à être chargé.
app.module.ts
import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { AppController } from "./app.controller"
import { AppService } from "./app.service"
@Module({
imports: [
MongooseModule.forRoot(
"mongodb+srv://<username>:<password>@cluster0.kngtf.mongodb.net/session-auth?retryWrites=true&w=majority"
),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Créer un module d'utilisateurs
Pour des raisons de séparation, pour rendre votre code propre et bien organisé, créez un module spécifiquement pour les utilisateurs utilisant la CLI NestJS en exécutant la commande suivante :
nest g module users
La commande ci-dessus crée un users
dossier avec users.module.ts
et met à jour app.module.ts
Créez également users.service.ts
et users.controller.ts
fichiers avec les commandes suivantes :
nest g service users
nest g controller users
Notez que vous pouvez créer vos dossiers et fichiers manuellement sans utiliser la CLI Nest, mais l'utilisation de la CLI met automatiquement à jour les dossiers nécessaires et vous facilite la vie.
Créer un schéma utilisateur
L'étape suivante consiste à créer votre UserSchema, mais d'abord, ajoutez un users.model.ts
fichier, où vous allez créer UserSchema
Cela devrait être la forme de notre application src
dossier maintenant.
└───src
│ └───users
│ │ └───users.controller.ts
│ │ └───users.model.ts
│ │ └───users.module.ts
│ │ └───users.service.ts
│ └───app.controller.ts
│ └───app.module.ts
│ └───app.service.ts
│ └───main.ts
Pour créer UserSchema
, importez tout en tant que mongoose à partir du package mongoose dans users.model.ts
. Appelez ensuite le nouveau schéma de mangouste, un plan du modèle utilisateur, et transmettez un objet JavaScript dans lequel vous définirez l'objet utilisateur et les données.
users.model.ts
import * as mongoose from "mongoose"
export const UserSchema = new mongoose.Schema(
{
username: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{ timestamps: true }
)
export interface User extends mongoose.Document {
_id: string;
username: string;
password: string;
}
Créez également une interface pour votre modèle qui étend mongoose, un document qui vous aide à remplir vos collections MongoDB.
Rendez-vous sur votre users.module.ts
et importez MongooseModule
dans le tableau des importations. Appelez ensuite le forFeature()
méthode fournie par MongooseModule
, et passez un tableau d'objets qui prend le nom et le schéma.
Cela vous permettra de partager le fichier n'importe où à l'aide de l'injection de dépendances.
users.module.ts
import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { UsersController } from "./users.controller"
import { UserSchema } from "./users.model"
import { UsersService } from "./users.service"
@Module({
imports: [MongooseModule.forFeature([{ name: "user", schema: UserSchema }])],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
Dans users.module.ts
, exportez le UsersService
pour vous permettre d'y accéder dans un autre module.
users.module.ts
import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { UsersController } from "./users.controller"
import { UserSchema } from "./users.model"
import { UsersService } from "./users.service"
@Module({
imports: [MongooseModule.forFeature([{ name: "user", schema: UserSchema }])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
C'est généralement une bonne idée d'encapsuler la logique métier dans une classe distincte. Une telle classe est connue sous le nom de service. Le travail de cette classe est de traiter les requêtes du contrôleur et d'exécuter la logique métier.
Dans users.service.ts
fichier, importer Model
de mongoose
, User
de users.model.ts
, et InjectModel
de @nestjs/mongoose
. Ajoutez ensuite une méthode au UsersService
classe qui prend un nom d'utilisateur et un mot de passe, et appelle la méthode insertUser()
.
users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './users.model';
@Injectable()
export class UsersService {
constructor(@InjectModel('user') private readonly userModel: Model<User>) {}
async insertUser(userName: string, password: string) {
const username = userName.toLowerCase();
const newUser = new this.userModel({
username,
password,
});
await newUser.save();
return newUser;
}
}
Maintenant que le UsersService
class est prête, vous devez l'injecter dans votre contrôleur. Mais d'abord, parlons du stockage sécurisé des mots de passe des utilisateurs.
L'aspect le plus critique de la procédure d'enregistrement est le mot de passe des utilisateurs, qui ne doit pas être enregistré en clair. Il est de la responsabilité de l'utilisateur de créer un mot de passe fort, mais il est de votre obligation en tant que développeur de garder ses mots de passe sécurisés. En cas de violation de la base de données, les mots de passe des utilisateurs seraient exposés. Et que se passe-t-il s'il est stocké en texte brut ? Je crois que vous connaissez la réponse. Pour résoudre ce problème, hachez les mots de passe à l'aide de bcrypt.
Alors, installez bcrypt
et @types/bcrypt
avec la commande suivante :
npm install @types/bcrypt bcrypt
Avec cela à l'écart, configurez votre contrôleur. Tout d'abord, importez votre UsersService
classe et tout de bcrypt
. Ajoutez ensuite un constructeur et une méthode permettant d'ajouter un utilisateur; il gérera les demandes de publication entrantes, appelez-le addUser
, avec un corps de fonction où vous hacherez le mot de passe.
users.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import * as bcrypt from 'bcrypt';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
//post / signup
@Post('/signup')
async addUser(
@Body('password') userPassword: string,
@Body('username') userName: string,
) {
const saltOrRounds = 10;
const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
const result = await this.usersService.insertUser(
userName,
hashedPassword,
);
return {
msg: 'User successfully registered',
userId: result.id,
userName: result.username
};
}
}
L'enregistrement se fait dans le app.module.ts
fichier, qui est obtenu en ajoutant le UsersModule
au @Module()
tableau des importations du décorateur dans app.module.ts
.
app.module.ts
import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { AppController } from "./app.controller"
import { AppService } from "./app.service"
import { UsersModule } from "./users/users.module"
@Module({
imports: [
MongooseModule.forRoot(
"mongodb+srv://<username>:<password>@cluster0.kngtf.mongodb.net/session-auth?retryWrites=true&w=majority"
),
UsersModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Toutes nos félicitations! Vous avez terminé l'enregistrement. Vous pouvez maintenant enregistrer un utilisateur avec un nom d'utilisateur et un mot de passe.
Maintenant, avec l'enregistrement terminé, ajoutez un getUser
fonction à votre UsersService
avec le findOne
méthode pour trouver un utilisateur par nom d'utilisateur.
users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './users.model';
@Injectable()
export class UsersService {
constructor(@InjectModel('user') private readonly userModel: Model<User>) {}
async insertUser(userName: string, password: string) {
const username = userName.toLowerCase();
const newUser = new this.userModel({
username,
password,
});
await newUser.save();
return newUser;
}
async getUser(userName: string) {
const username = userName.toLowerCase();
const user = await this.userModel.findOne({ username });
return user;
}
}
Créer un module d'authentification
Comme pour les utilisateurs, créez un module et un service d'authentification spécifiquement pour toutes les authentifications/vérifications. Pour ce faire, exécutez les commandes suivantes :
nest g module auth
nest g service auth
Ce qui précède créera un dossier auth, auth.module.ts
, et auth.service.ts
, et mettez à jour le auth.module.ts
et app.module.ts
fichiers.
À ce stade, la forme de votre application src
dossier doit ressembler à ceci.
└───src
│ └───auth
│ │ └───auth.module.ts
│ │ └───auth.service.ts
│ └───users
│ │ └───users.controller.ts
│ │ └───users.model.ts
│ │ └───users.module.ts
│ │ └───users.service.ts
│ └───app.controller.ts
│ └───app.module.ts
│ └───app.service.ts
│ └───main.ts
La commande de génération ci-dessus mettra à jour votre app.module.ts
, et il ressemblera à l'extrait de code ci-dessous :
app.module.ts
import { Module } from "@nestjs/common"
import { MongooseModule } from "@nestjs/mongoose"
import { AppController } from "./app.controller"
import { AppService } from "./app.service"
import { UsersModule } from "./users/users.module"
import { AuthModule } from './auth/auth.module';
@Module({
imports: [UsersModule, AuthModule, MongooseModule.forRoot(
//database url string
'mongodb://localhost:27017/myapp'
)],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Authentifier les utilisateurs
Accédez à votre auth.module.ts
fichier et ajoutez UsersModule
dans le tableau des importations pour permettre l'accès au UsersService
exporté depuis le users.module.ts
fichier.
auth.module.ts
import { Module } from "@nestjs/common"
import { UsersModule } from "src/users/users.module"
import { AuthService } from "./auth.service"
@Module({
imports: [UsersModule],
providers: [AuthService],
})
export class AuthModule {}
Dans votre auth.service.ts
fichier, appelez le constructeur afin que vous puissiez injecter le UsersService
, et ajoutez une méthode de validation qui prendra un nom d'utilisateur et un mot de passe.
Pour ajouter des validations de base, vérifiez si l'utilisateur existe dans la base de données et comparez le mot de passe donné avec celui de votre base de données pour vous assurer qu'il correspond. S'il existe, retourner l'utilisateur dans le request.user
objet — sinon, renvoie null.
auth.service.ts
import { Injectable, NotAcceptableException } from '@nestjs/common';
import { UsersService } from 'src/users/users.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(private readonly usersService: UsersService) {}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.usersService.getUser(username);
const passwordValid = await bcrypt.compare(password, user.password)
if (!user) {
throw new NotAcceptableException('could not find the user');
}
if (user && passwordValid) {
return {
userId: user.id,
userName: user.username
};
}
return null;
}
}
Pour aller plus loin, créez un nouveau fichier et nommez-le local.strategy.ts
. Ce fichier représentera la stratégie de Passport.js
, que vous avez installé précédemment, c'est la local strategy
. Et à l'intérieur, passez la stratégie, qui est la Strategy
de passport-local
.
Créez un constructeur et injectez le AuthService
, appelez le super()
méthode; assurez-vous d'appeler le super()
méthode.
local.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const userName = username.toLowerCase();
const user = await this.authService.validateUser(userName, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Retournez à votre auth.module.ts
dossier. Ajoutez ensuite PassportModule
aux importations et LocalStrategy
aux fournisseurs.
auth.module.ts
import { Module } from "@nestjs/common"
import { PassportModule } from "@nestjs/passport"
import { UsersModule } from "src/users/users.module"
import { AuthService } from "./auth.service"
import { LocalStrategy } from "./local.strategy"
@Module({
imports: [UsersModule, PassportModule],
providers: [AuthService, LocalStrategy],
})
export class AuthModule {}
Maintenant, ajoutez la route de connexion à votre users.controller.ts
:
users.controller.ts
import {
Body,
Controller,
Post,
Request,
} from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
//post / signup
@Post('/signup')
async addUser(
@Body('password') userPassword: string,
@Body('username') userName: string,
) {
const saltOrRounds = 10;
const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
const result = await this.usersService.insertUser(
userName,
hashedPassword,
);
return {
msg: 'User successfully registered',
userId: result.id,
userName: result.username
};
}
//Post / Login
@Post('/login')
login(@Request() req): any {
return {User: req.user,
msg: 'User logged in'};
}
}
Maintenant que vous avez tout mis en place, vous ne pouvez toujours pas connecter un utilisateur car il n'y a rien pour déclencher la route de connexion. Ici, utilisez des gardes pour y parvenir.
Créez un fichier et nommez-le local.auth.guard.ts
, puis une classe LocalAuthGuard
qui étend AuthGuard
depuis NestJS/passport
, où vous fournirez le nom de la stratégie et passerez le nom de votre stratégie, local
.
local.auth.guard.ts.
import { Injectable } from "@nestjs/common"
import { AuthGuard } from "@nestjs/passport"
@Injectable()
export class LocalAuthGuard extends AuthGuard("local") {}
Ajoutez le UseGuard
décorateur à votre route de connexion dans le users.controller.ts
fichier, et transmettez le LocalAuthGuard
.
users.controller.ts
import {
Body,
Controller,
Post,
UseGuards,
Request,
} from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { LocalAuthGuard } from 'src/auth/local.auth.guard';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
//post / signup
@Post('/signup')
async addUser(
@Body('password') userPassword: string,
@Body('username') userName: string,
) {
const saltOrRounds = 10;
const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
const result = await this.usersService.insertUser(
userName,
hashedPassword,
);
return {
msg: 'User successfully registered',
userId: result.id,
userName: result.username
};
}
//Post / Login
@UseGuards(LocalAuthGuard)
@Post('/login')
login(@Request() req): any {
return {User: req.user,
msg: 'User logged in'};
}
}
Enfin, vous pouvez connecter un utilisateur avec un nom d'utilisateur et un mot de passe enregistrés.
Protéger les routes d'authentification
Vous avez configuré avec succès l'authentification des utilisateurs. Désormais, protégez vos itinéraires contre les accès non autorisés en limitant l'accès aux seuls utilisateurs authentifiés. Accédez à votre users.controller.ts
file, et ajoutez une autre route - nommez-la 'protected' et faites-lui renvoyer le req.user
objet.
users.controller.ts
import {
Body,
Controller,
Get,
Post,
UseGuards,
Request,
} from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { LocalAuthGuard } from 'src/auth/local.auth.guard';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
//signup
@Post('/signup')
async addUser(
@Body('password') userPassword: string,
@Body('username') userName: string,
) {
const saltOrRounds = 10;
const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
const result = await this.usersService.insertUser(
userName,
hashedPassword,
);
return {
msg: 'User successfully registered',
userId: result.id,
userName: result.username
};
}
//Post / Login
@UseGuards(LocalAuthGuard)
@Post('/login')
login(@Request() req): any {
return {User: req.user,
msg: 'User logged in'};
}
// Get / protected
@Get('/protected')
getHello(@Request() req): string {
return req.user;
}
}
La route protégée dans le code ci-dessus renverra un objet vide au lieu de renvoyer les détails de l'utilisateur lorsqu'un utilisateur connecté lui fait une demande car il a déjà perdu la connexion.
Pour que tout soit trié, c'est là qu'intervient l'authentification basée sur la session.
Dans l'authentification basée sur la session, lorsqu'un utilisateur se connecte, l'utilisateur est enregistré dans une session afin que toute demande ultérieure de l'utilisateur après la connexion récupère les détails de la session et accorde à l'utilisateur un accès facile. La session expire lorsque l'utilisateur se déconnecte.
Pour démarrer l'authentification basée sur la session, installez express-session et les types NestJS à l'aide de la commande suivante :
npm install express-session @types/express-session
Lorsque l'installation est terminée, accédez à votre main.ts
file, la racine de votre application, et faites-y les configurations.
Importez tout depuis passport
et express-session
, puis ajoutez l'initialisation du passeport et la session du passeport.
Il est préférable de conserver votre clé secrète dans vos variables d'environnement.
main.ts
import { NestFactory } from "@nestjs/core"
import { AppModule } from "./app.module"
import * as session from "express-session"
import * as passport from "passport"
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.use(
session({
secret: "keyboard",
resave: false,
saveUninitialized: false,
})
)
app.use(passport.initialize())
app.use(passport.session())
await app.listen(3000)
}
bootstrap()
Ajouter un nouveau fichier, authenticated.guard.ts
, dans votre auth
dossier. Et créez un nouveau Guard qui vérifie s'il existe une session pour l'utilisateur faisant la demande - nommez-le authenticatedGuard
.
authenticated.guard.ts
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"
@Injectable()
export class AuthenticatedGuard implements CanActivate {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest()
return request.isAuthenticated()
}
}
Dans le code ci-dessus, la demande est obtenue à partir du contexte et vérifiée si elle est authentifiée. isAuthenticated()
vient de passport.js
automatiquement ; ça dit. "Hey ! Existe-t-il une session pour cet utilisateur ? Si oui, continuez."
Pour déclencher la connexion, dans votre users.controller.ts
fichier :
- importer
authenticated
deauthenticated.guard.ts
; - ajoutez le
useGuard
décorateur auprotected
itinéraire; et, - transmettre
AuthenticatedGuard
.
users.controller.ts
import {
Body,
Controller,
Get,
Post,
UseGuards,
Request,
} from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { AuthenticatedGuard } from 'src/auth/authenticated.guard';
import { LocalAuthGuard } from 'src/auth/local.auth.guard';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
//signup
@Post('/signup')
async addUser(
@Body('password') userPassword: string,
@Body('username') userName: string,
) {
const saltOrRounds = 10;
const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
const result = await this.usersService.insertUser(
userName,
hashedPassword,
);
return {
msg: 'User successfully registered',
userId: result.id,
userName: result.username
};
}
//Post / Login
@UseGuards(LocalAuthGuard)
@Post('/login')
login(@Request() req): any {
return {User: req.user,
msg: 'User logged in'};
}
//Get / protected
@UseGuards(AuthenticatedGuard)
@Get('/protected')
getHello(@Request() req): string {
return req.user;
}
}
À ce stade, cela échoue toujours car vous n'avez configuré que express-session
mais ne l'a pas implémenté.
Lorsqu'un utilisateur se connecte, vous devez enregistrer l'utilisateur dans une session afin que l'utilisateur puisse accéder à d'autres routes avec la session.
Une chose à garder à l'esprit est que par défaut, le express-session
bibliothèque stocke la session dans la mémoire du serveur Web.
Avant d'entrer dans la session, vous devez sérialiser l'utilisateur. À la sortie de la session, désérialisez l'utilisateur.
Donc, créez un nouveau fichier dans le dossier auth pour le sérialiseur et le désérialiseur, nommez-le session.serializer.ts
.
À ce stade, la forme de notre application src
dossier devrait ressembler à ceci.
└───src
│ └───auth
│ │ └───auth.module.ts
│ │ └───auth.service.ts
│ │ └───authenticated.guard.ts
│ │ └───local.auth.guard.ts
│ │ └───local.strategy.ts
│ │ └───session.serializer.ts
│ └───users
│ │ └───users.controller.ts
│ │ └───users.model.ts
│ │ └───users.module.ts
│ │ └───users.service.ts
│ └───app.controller.ts
│ └───app.module.ts
│ └───app.service.ts
│ └───main.ts
session.serializer.ts
import { Injectable } from "@nestjs/common"
import { PassportSerializer } from "@nestjs/passport"
@Injectable()
export class SessionSerializer extends PassportSerializer {
serializeUser(user: any, done: (err: Error, user: any) => void): any {
done(null, user)
}
deserializeUser(
payload: any,
done: (err: Error, payload: string) => void
): any {
done(null, payload)
}
}
Retournez à votre auth.module.ts
fichier, fournissez le SessionSerializer
, et ajoutez le register
méthode au PassportModule
.
auth.module.ts
import { Module } from "@nestjs/common"
import { PassportModule } from "@nestjs/passport"
import { UsersModule } from "src/users/users.module"
import { AuthService } from "./auth.service"
import { LocalStrategy } from "./local.strategy"
import { SessionSerializer } from "./session.serializer"
@Module({
imports: [UsersModule, PassportModule.register({ session: true })],
providers: [AuthService, LocalStrategy, SessionSerializer],
})
export class AuthModule {}
Ajoutez des codes dans le LocalAuthGuard
dans le local.auth.guard.ts
fichier.
Call the login
method in super
and pass in the request to trigger the actual login by creating a session. If you want to use sessions, you must remember to trigger the super.login()
.
local.auth.guard.ts
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
async canActivate(context: ExecutionContext) {
const result = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return result;
}
}
If you log in now, you will see the session ID stored in a cookie, which is just a key to the session store, and the cookie gets saved in the browser. The cookie is automatically attached to the rest of the request.
Now that the session is working, you can access the protected route; it will return the expected user’s details.
Logout Users
As mentioned earlier, once a user logs out, you destroy all sessions.
To log out a user, go to the users.controller.ts
file, add a logout route, and call the req.session.session()
méthode. You can return a message notifying that the user’s session has ended.
import {
Body,
Controller,
Get,
Post,
UseGuards,
Request,
} from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { AuthenticatedGuard } from 'src/auth/authenticated.guard';
import { LocalAuthGuard } from 'src/auth/local.auth.guard';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
//signup
@Post('/signup')
async addUser(
@Body('password') userPassword: string,
@Body('username') userName: string,
) {
const saltOrRounds = 10;
const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
const result = await this.usersService.insertUser(
userName,
hashedPassword,
);
return {
msg: 'User successfully registered',
userId: result.id,
userName: result.username
};
}
//Post / Login
@UseGuards(LocalAuthGuard)
@Post('/login')
login(@Request() req): any {
return {User: req.user,
msg: 'User logged in'};
}
//Get / protected
@UseGuards(AuthenticatedGuard)
@Get('/protected')
getHello(@Request() req): string {
return req.user;
}
//Get / logout
@Get('/logout')
logout(@Request() req): any {
req.session.destroy();
return { msg: 'The user session has ended' }
}
}
So, once you log out, it returns a message notifying you that the user session has ended. The code for this tutorial is hosted here on my Github repository.
Test Your Application
You have successfully implemented user signup, authentication, and protected the route to enable authorized access only.
It’s time to test the application. If everything is in order, your server should be running. Else, restart your server with the following command:
npm run start:dev
Head over to your Postman. And let’s finally test our application.
Sign Up As a User
Log In As a User
Logged-in User’s Cookie ID
Request the Protected Route
User Logout
Alternatively, Implement User Authentication with LoginRadius
LoginRadius provides a variety of registration and authentication services to assist you in better connecting with your consumers.
On any web or mobile application, LoginRadius is the developer-friendly Identity Platform that delivers a complete set of APIs for authentication, identity verification, single sign-on, user management, and account protection capabilities like multi-factor authentication.
To implement LoginRadius in your NestJS application, follow this tutorial:NestJS User Authentication with LoginRadius API.
Conclusion
Toutes nos félicitations! In this tutorial, you've learned how to implement session-based authentication in a NestJS application with the MongoDB database. You've created and authenticated a user and protected your routes from unauthorized access.
You can access the sample code used in this tutorial on GitHub.
Remarque : Session storage is saved by default in 'MemoryStore,' which is not intended for production use. So, while no external datastore is required for development, once in production, a data store such as Redis or another is suggested for stability and performance. You can learn more about session storage here.