Inleiding
Het is een onbetwistbare realiteit dat authenticatie van cruciaal belang is in elke toepassing of elk systeem als u gebruikersgegevens wilt beveiligen en veilige toegang tot informatie wilt mogelijk maken. Authenticatie is de procedure om vast te stellen of aan te tonen dat iets waar, legitiem of geldig is.
Vereisten
Deze tutorial is een praktische demonstratie. Zorg ervoor dat u over het volgende beschikt om mee te volgen:
- Node.js draait op je systeem omdat NestJS een Node.js-framework is
- MongoDB geïnstalleerd
Wat is NestJS?
Nest (NestJS) is een Node.js server-side applicatieframework voor het bouwen van schaalbare, efficiënte applicaties.
Het is geschreven in TypeScript en gebouwd op Express, een zeer minimalistisch raamwerk dat op zichzelf geweldig is, maar geen structuur heeft. Het combineert programmeerparadigma's zoals objectgeoriënteerd programmeren, functioneel programmeren en functioneel reactief programmeren.
Het is een framework om te gebruiken als je veel structuur op je backend wilt. De syntaxis en structuur lijken erg op AngularJS, een front-end framework. En het gebruikt TypeScript, services en afhankelijkheidsinjectie op dezelfde manier als AngularJS.
Het maakt gebruik van modules en controllers, en je kunt controllers voor een bestand bouwen met behulp van de opdrachtregelinterface.
Met NestJS-modules kun je gerelateerde controllers en serviceproviders in één codebestand groeperen. Simpel gezegd, een NestJS-module is een TypeScript-bestand met de @Module annotatie (). Deze decorateur informeert het NestJS-framework over welke controllers, serviceproviders en andere bijbehorende bronnen later door de app-code zullen worden geïnstantieerd en gebruikt.
Wat is op sessie gebaseerde authenticatie?
Sessiegebaseerde authenticatie is een methode voor gebruikersauthenticatie waarbij de server een sessie maakt na een succesvolle aanmelding, waarbij de sessie-ID is opgeslagen in een cookie of lokale opslag in uw browser.
Bij volgende verzoeken wordt uw cookie gevalideerd tegen de sessie-ID die op de server is opgeslagen. Als er een match is, wordt het verzoek als geldig beschouwd en verwerkt.
Bij het gebruik van deze authenticatiemethode is het van cruciaal belang om de volgende best practices op het gebied van beveiliging in gedachten te houden:
- Genereer lange en willekeurige sessie-ID's (128 bits is de aanbevolen lengte) om brute force-aanvallen ondoeltreffend te maken
- Vermijd het opslaan van gevoelige of gebruikersspecifieke gegevens
- HTTPS-communicatie verplicht maken voor alle op sessies gebaseerde apps
- Cookies maken met veilige en alleen HTTP-kenmerken
Waarom authenticatie op basis van sessies?
Sessiegebaseerde authenticatie is veiliger dan de meeste authenticatiemethoden omdat het eenvoudig en veilig is en een beperkte opslagruimte heeft. Het wordt ook beschouwd als de beste optie voor websites in hetzelfde hoofddomein.
Projectconfiguratie
Start je projectconfiguratie door Nest CLI wereldwijd te installeren. Je hoeft dit niet te doen als je NestJS CLI al hebt geïnstalleerd.
De Nest CLI is een opdrachtregelinterfacetool voor het instellen, ontwikkelen en onderhouden van Nest-apps.
npm i -g @nestjs/cli
Laten we nu uw project opzetten door de volgende opdracht uit te voeren:
nest new session-based-auth
Met de bovenstaande opdracht wordt een Nest-applicatie gemaakt met enkele boilerplates en wordt u gevraagd de pakketbeheerder van uw voorkeur te kiezen om de vereiste modules te installeren om uw applicatie uit te voeren. Voor demonstratie gebruikt deze tutorial npm . Druk op de enter-toets om door te gaan met npm .
Als alles goed is gegaan, zou je een uitvoer zoals die op de onderstaande schermafbeelding op je terminal moeten zien.
Zodra de installatie is voltooid, gaat u naar uw projectdirectory en voert u de toepassing uit met de onderstaande opdracht:
npm run start:dev
De bovenstaande opdracht voert de toepassing uit en let op wijzigingen. Uw project src
mappenstructuur zou er als volgt uit moeten zien.
└───src
│ └───app.controller.ts
│ └───app.modules.ts
│ └───app.service.ts
│ └───main.ts
Afhankelijkheden installeren
Nu uw toepassing is ingesteld, gaan we de benodigde afhankelijkheden installeren.
npm install --save @nestjs/passport passport passport-local
De bovenstaande opdracht installeert Passport.js, een populaire nest.js-authenticatiebibliotheek.
Installeer ook de typen voor de strategie met de onderstaande opdracht:
Het bevat typedefinities voor passport-local
.
npm install --save-dev @types/passport-local
MongoDB-database instellen in NestJS
Om je database in te stellen en te verbinden, installeer je het Mongoose-pakket en de NestJS-wrapper met de volgende opdracht:
npm install --save @nestjs/mongoose mongoose
De Mongoose NestJS-wrapper helpt je Mongoose te gebruiken in de NestJS-applicatie en geeft goedgekeurde TypeScript-ondersteuning.
Ga nu naar je app.module.ts
, en importeer de mongoose
module van @nestjs/mongoose
. Roep vervolgens de forRoot()
. aan methode, een methode die wordt geleverd door de Mongoose-module, en geef de string van uw database-URL door.
Uw databaseverbinding instellen in app.module.ts
helpt uw applicatie om onmiddellijk verbinding te maken met de database wanneer de server start - na het uitvoeren van uw applicatie, aangezien dit de eerste module is die wordt geladen.
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 {}
Gebruikersmodule maken
Voor problemen met de scheiding, om uw code schoon en goed georganiseerd te maken, maakt u een module speciaal voor gebruikers die de NestJS CLI gebruiken door de volgende opdracht uit te voeren:
nest g module users
De bovenstaande opdracht maakt een users
. aan map met users.module.ts
en updates app.module.ts
Maak ook users.service.ts
en users.controller.ts
bestanden met de volgende opdrachten:
nest g service users
nest g controller users
Houd er rekening mee dat u uw mappen en bestanden handmatig kunt maken zonder de nest-CLI te gebruiken, maar als u de CLI gebruikt, worden de benodigde mappen automatisch bijgewerkt en wordt uw leven gemakkelijker.
Gebruikersschema maken
De volgende stap is om uw UserSchema te maken, maar voeg eerst een users.model.ts
toe bestand, waar u UserSchema
. aanmaakt
Dit zou de vorm moeten zijn van onze applicatie src
map nu.
└───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
Om UserSchema
te maken , importeer alles als mangoest uit het mangoestpakket in users.model.ts
. Roep vervolgens het nieuwe mangoestschema aan, een blauwdruk van het gebruikersmodel, en geef een JavaScript-object door waarin u het gebruikersobject en de gegevens definieert.
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;
}
Maak ook een interface voor uw model die mangoest uitbreidt, een document waarmee u uw MongoDB-verzamelingen kunt vullen.
Ga naar je users.module.ts
en importeer MongooseModule
in de importarray. Roep vervolgens de forFeature()
. aan methode geleverd door MongooseModule
, en geef een array van objecten door die naam en schema aannemen.
Hierdoor kunt u het bestand overal delen met behulp van afhankelijkheidsinjectie.
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 {}
In users.module.ts
, exporteer de UsersService
om u in staat te stellen er toegang toe te krijgen in een andere 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 {}
Het is meestal een goed idee om de bedrijfslogica in een aparte klasse in te kapselen. Zo'n klasse staat bekend als een service. De taak van deze klasse is om de verzoeken van de controller te verwerken en de bedrijfslogica uit te voeren.
In users.service.ts
bestand, importeer Model
van mongoose
, User
van users.model.ts
, en InjectModel
van @nestjs/mongoose
. Voeg vervolgens een methode toe aan de UsersService
klasse waarvoor een gebruikersnaam en wachtwoord nodig zijn, en roep de methode insertUser()
. aan .
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;
}
}
Nu de UsersService
class klaar is, moet je het in je controller injecteren. Maar laten we het eerst hebben over het veilig opslaan van de wachtwoorden van de gebruikers.
Het meest kritische aspect van de registratieprocedure zijn de wachtwoorden van de gebruikers, die niet in platte tekst mogen worden opgeslagen. Het is de verantwoordelijkheid van de gebruiker om een sterk wachtwoord te maken, maar het is jouw plicht als ontwikkelaar om zijn wachtwoorden veilig te houden. Als er een database-inbreuk optreedt, zouden de wachtwoorden van de gebruikers worden onthuld. En wat gebeurt er als het in platte tekst wordt opgeslagen? Ik geloof dat je het antwoord weet. Om dit aan te pakken, hash je de wachtwoorden met bcrypt.
Installeer dus bcrypt
en @types/bcrypt
met het volgende commando:
npm install @types/bcrypt bcrypt
Met dat uit de weg, stelt u uw controller in. Importeer eerst uw UsersService
class en alles van bcrypt
. Voeg vervolgens een constructor toe en een methode waarmee u een gebruiker kunt toevoegen; het zal inkomende postverzoeken behandelen, noem het addUser
, met een functietekst waar u het wachtwoord kunt hashen.
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
};
}
}
De registratie gebeurt in de app.module.ts
bestand, wat wordt bereikt door het toevoegen van de UsersModule
naar de @Module()
de array van de importeur van de decorateur in 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 {}
Gefeliciteerd! U bent klaar met de registratie. U kunt nu een gebruiker registreren met een gebruikersnaam en wachtwoord.
Nu, met registratie uit de weg, voeg een getUser
. toe functie naar uw UsersService
met de findOne
methode om een gebruiker te vinden op gebruikersnaam.
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;
}
}
Verificatiemodule maken
Maak net als voor gebruikers een auth-module en -service specifiek voor alle authenticaties/verificaties. Voer hiervoor de volgende opdrachten uit:
nest g module auth
nest g service auth
Het bovenstaande zal een auth-map maken, auth.module.ts
, en auth.service.ts
, en update de auth.module.ts
en app.module.ts
bestanden.
Op dit punt is de vorm van uw applicatie src
map zou er als volgt uit moeten zien.
└───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
De bovenstaande Genereer-opdracht zal uw app.module.ts
bijwerken , en het ziet eruit als het onderstaande codefragment:
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 {}
Gebruikers authenticeren
Ga naar uw auth.module.ts
bestand en voeg UsersModule
. toe in de imports-array om toegang te krijgen tot de UsersService
geëxporteerd vanuit de users.module.ts
bestand.
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 {}
In uw auth.service.ts
bestand, roept u de constructor aan zodat u de UsersService
. kunt injecteren , en voeg een validatiemethode toe waarvoor een gebruikersnaam en wachtwoord nodig zijn.
Om enkele basisvalidaties toe te voegen, controleert u of de gebruiker in de database bestaat en vergelijkt u het gegeven wachtwoord met dat in uw database om er zeker van te zijn dat het overeenkomt. Als het bestaat, retourneer de gebruiker in de request.user
object — geef anders null terug.
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;
}
}
Ga verder, maak een nieuw bestand en noem het local.strategy.ts
. Dit bestand vertegenwoordigt de strategie van Passport.js
, die je eerder hebt geïnstalleerd, dat is de local strategy
. En daarbinnen geef je de strategie door, namelijk de Strategy
van passport-local
.
Maak een constructor en injecteer de AuthService
, bel de super()
methode; zorg ervoor dat u de super()
. aanroept methode.
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;
}
}
Ga terug naar je auth.module.ts
het dossier. Voeg vervolgens PassportModule
toe naar import en LocalStrategy
aan providers.
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 {}
Voeg nu de inlogroute toe aan uw 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'};
}
}
Nu u dit allemaal hebt ingevoerd, kunt u nog steeds niet inloggen als gebruiker omdat er niets is om de inlogroute te activeren. Gebruik hier bewakers om dat te bereiken.
Maak een bestand en noem het local.auth.guard.ts
, dan een klasse LocalAuthGuard
dat verlengt AuthGuard
van NestJS/passport
, waar u de naam van de strategie opgeeft en de naam van uw strategie doorgeeft, local
.
local.auth.guard.ts.
import { Injectable } from "@nestjs/common"
import { AuthGuard } from "@nestjs/passport"
@Injectable()
export class LocalAuthGuard extends AuthGuard("local") {}
Voeg de UseGuard
toe decorateur naar uw inlogroute in de users.controller.ts
bestand, en geef de LocalAuthGuard
. door .
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'};
}
}
Ten slotte kunt u zich aanmelden als gebruiker met een geregistreerde gebruikersnaam en wachtwoord.
Verificatieroutes beschermen
U hebt de gebruikersauthenticatie met succes ingesteld. Bescherm nu uw routes tegen ongeoorloofde toegang door de toegang te beperken tot alleen geverifieerde gebruikers. Ga naar uw users.controller.ts
bestand, en voeg een andere route toe — noem het 'beschermd' en laat het de req.user
retourneren voorwerp.
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;
}
}
De beschermde route in de bovenstaande code retourneert een leeg object in plaats van de gegevens van de gebruiker terug te sturen wanneer een ingelogde gebruiker een verzoek doet omdat hij de login al kwijt is.
Om dat te regelen, komt hier de op sessies gebaseerde authenticatie om de hoek kijken.
Bij authenticatie op basis van sessies, wanneer een gebruiker inlogt, wordt de gebruiker in een sessie opgeslagen, zodat elk volgend verzoek van de gebruiker na het inloggen de details van de sessie zal ophalen en de gebruiker gemakkelijke toegang zal verlenen. De sessie verloopt wanneer de gebruiker uitlogt.
Om op sessie gebaseerde verificatie te starten, installeert u express-session en de NestJS-typen met behulp van de volgende opdracht:
npm install express-session @types/express-session
Wanneer de installatie is voltooid, gaat u naar uw main.ts
bestand, de root van uw applicatie, en voer de configuraties daar uit.
Importeer alles uit passport
en express-session
en voeg vervolgens paspoortinitialisatie en paspoortsessie toe.
Het verdient de voorkeur om uw geheime sleutel in uw omgevingsvariabelen te bewaren.
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()
Voeg een nieuw bestand toe, authenticated.guard.ts
, in uw auth
map. En maak een nieuwe Guard die controleert of er een sessie is voor de gebruiker die het verzoek doet — noem het 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()
}
}
In de bovenstaande code wordt het verzoek uit de context gehaald en gecontroleerd of het is geverifieerd. isAuthenticated()
komt van passport.js
automatisch; het zegt. "hey! bestaat er een sessie voor deze gebruiker? Zo ja, ga dan door."
Om de login te activeren, in uw users.controller.ts
bestand:
- importeer
authenticated
vanauthenticated.guard.ts
; - voeg de
useGuard
toe decorateur naar deprotected
route; en, - geef
AuthenticatedGuard
in .
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;
}
}
Op dit moment mislukt het nog steeds omdat je alleen express-session
. hebt geconfigureerd maar heb het niet geïmplementeerd.
Wanneer een gebruiker inlogt, moet je de gebruiker in een sessie opslaan zodat de gebruiker toegang heeft tot andere routes met de sessie.
Een ding om in gedachten te houden is dat standaard de express-session
bibliotheek slaat de sessie op in het geheugen van de webserver.
Voordat het in de sessie gaat, moet u de gebruiker serialiseren. Deserialiseer de gebruiker zodra deze uit de sessie komt.
Maak dus een nieuw bestand in de auth-map voor serializer en deserializer, noem het session.serializer.ts
.
Op dit punt is de vorm van onze applicatie src
map zou er zo uit moeten zien.
└───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)
}
}
Ga terug naar je auth.module.ts
bestand, geef de SessionSerializer
, en voeg het register
. toe methode naar de 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 {}
Add some codes within the LocalAuthGuard
in the local.auth.guard.ts
bestand.
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()
methode. 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
Gefeliciteerd! 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.
Opmerking: 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.