Introduktion
Det är en obestridlig verklighet att autentisering är avgörande i alla applikationer eller system om du vill säkra användardata och möjliggöra säker åtkomst till information. Autentisering är proceduren för att fastställa eller demonstrera att något är sant, legitimt eller giltigt.
Förutsättningar
Denna handledning är en praktisk demonstration. För att följa med, se till att du har följande på plats:
- Node.js körs i ditt system eftersom NestJS är ett Node.js-ramverk
- MongoDB installerad
Vad är NestJS?
Nest (NestJS) är ett Node.js-applikationsramverk på serversidan för att bygga skalbara, effektiva applikationer.
Det är skrivet i TypeScript och byggt på Express, ett väldigt minimalistiskt ramverk som är utmärkt i sig men saknar struktur. Den kombinerar programmeringsparadigm som objektorienterad programmering, funktionell programmering och funktionell reaktiv programmering.
Det är ett ramverk att använda om du vill ha mycket struktur på din backend. Dess syntax och struktur är mycket lik AngularJS, ett front-end-ramverk. Och den använder TypeScript, tjänster och beroendeinjektion på samma sätt som AngularJS gör.
Den använder moduler och kontroller, och du kan bygga kontroller för en fil med hjälp av kommandoradsgränssnittet.
NestJS-moduler låter dig gruppera relaterade kontroller och tjänsteleverantörer i en enda kodfil. Enkelt uttryckt är en NestJS-modul en TypeScript-fil med @Module anteckning (). Den här dekoratören informerar NestJS-ramverket om vilka kontroller, tjänsteleverantörer och andra associerade resurser som kommer att instansieras och användas av appkoden senare.
Vad är sessionsbaserad autentisering?
Sessionsbaserad autentisering är en metod för användarautentisering där servern skapar en session efter en lyckad inloggning, med sessions-ID lagrat i en cookie eller lokal lagring i din webbläsare.
Vid efterföljande förfrågningar valideras din cookie mot det sessions-ID som finns lagrat på servern. Om det finns en matchning anses begäran vara giltig och behandlad.
När du använder den här autentiseringsmetoden är det viktigt att ha följande säkerhetspraxis i åtanke:
- Generera långa och slumpmässiga sessions-ID:n (128 bitar är den rekommenderade längden) för att göra brute force-attacker ineffektiva
- Undvik att lagra känslig eller användarspecifik data
- Gör HTTPS-kommunikation obligatorisk för alla sessionsbaserade appar
- Skapa cookies som har säkra och endast HTTP-attribut
Varför sessionsbaserad autentisering?
Sessionsbaserad autentisering är säkrare än de flesta autentiseringsmetoder eftersom den är enkel, säker och har en begränsad lagringsstorlek. Det anses också vara det bästa alternativet för webbplatser i samma rotdomän.
Projektinställning
Starta din projektkonfiguration genom att installera Nest CLI globalt. Du behöver inte göra detta om du redan har NestJS CLI installerat.
Nest CLI är ett kommandoradsgränssnittsverktyg för att ställa in, utveckla och underhålla Nest-applikationer.
npm i -g @nestjs/cli
Låt oss nu ställa in ditt projekt genom att köra följande kommando:
nest new session-based-auth
Ovanstående kommando skapar en Nest-applikation med några boilerplates och uppmanar dig sedan att välja din föredragna pakethanterare för att installera de nödvändiga modulerna för att köra din applikation. För demonstration använder den här handledningen npm . Tryck på enter-tangenten för att fortsätta med npm .
Om allt gick bra bör du se en utdata som den på skärmdumpen nedan på din terminal.
När installationen är klar, flytta in i din projektkatalog och kör programmet med kommandot nedan:
npm run start:dev
Ovanstående kommando kör programmet och tittar efter ändringar. Ditt projekt src
mappstrukturen bör se ut som följer.
└───src
│ └───app.controller.ts
│ └───app.modules.ts
│ └───app.service.ts
│ └───main.ts
Installationsberoenden
Nu när din applikation är konfigurerad, låt oss installera de beroenden som behövs.
npm install --save @nestjs/passport passport passport-local
Kommandot ovan installerar Passport.js, ett populärt nest.js-autentiseringsbibliotek.
Installera också typerna för strategin med kommandot nedan:
Den innehåller typdefinitioner för passport-local
.
npm install --save-dev @types/passport-local
Konfigurera MongoDB-databas i NestJS
För att ställa in och ansluta din databas, installera Mongoose-paketet och NestJS-omslaget med följande kommando:
npm install --save @nestjs/mongoose mongoose
Mongoose NestJS-omslaget hjälper dig att använda Mongoose i NestJS-applikationen och ger godkänt TypeScript-stöd.
Gå nu över till din app.module.ts
, och importera mongoose
modul från @nestjs/mongoose
. Anropa sedan forRoot()
metod, en metod som tillhandahålls av Mongoose-modulen, och skicka in din databas URL-sträng.
Konfigurera din databasanslutning i app.module.ts
hjälper din applikation att ansluta till databasen omedelbart när servern startar - efter att du har kört din applikation eftersom det är den första modulen som laddas.
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 {}
Skapa användarmodul
För separationsproblem, för att göra din kod ren och välorganiserad, skapa en modul specifikt för användare som använder NestJS CLI genom att köra följande kommando:
nest g module users
Kommandot ovan skapar en users
mapp med users.module.ts
och uppdaterar app.module.ts
Skapa även users.service.ts
och users.controller.ts
filer med följande kommandon:
nest g service users
nest g controller users
Observera att du kan skapa dina mappar och filer manuellt utan att använda nest CLI, men att använda CLI uppdaterar automatiskt nödvändiga mappar och gör ditt liv enklare.
Skapa användarschema
Nästa steg är att skapa ditt UserSchema, men lägg först till en users.model.ts
fil, där du skapar UserSchema
Detta bör vara formen på vår applikation src
mapp 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
För att skapa UserSchema
, importera allt som mongoose från mongoose-paketet i users.model.ts
. Anropa sedan det nya mongoose-schemat, en ritning av användarmodellen, och skicka in ett JavaScript-objekt där du kommer att definiera användarobjektet och data.
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;
}
Skapa också ett gränssnitt för din modell som utökar mongoose, ett dokument som hjälper dig att fylla i dina MongoDB-samlingar.
Gå över till din users.module.ts
och importera MongooseModule
i importmatrisen. Anropa sedan forFeature()
metod tillhandahållen av MongooseModule
, och skicka in en array av objekt som tar in namn och schema.
Detta gör att du kan dela filen var som helst med hjälp av beroendeinjektion.
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 {}
I users.module.ts
, exportera UsersService
för att du ska kunna komma åt den i en annan modul.
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 {}
Det är vanligtvis en bra idé att kapsla in affärslogiken i en separat klass. En sådan klass är känd som en tjänst. Den här klassens uppgift är att behandla kontrollantens förfrågningar och utföra affärslogiken.
I users.service.ts
fil, importera Model
från mongoose
, User
från users.model.ts
och InjectModel
från @nestjs/mongoose
. Lägg sedan till en metod i UsersService
klass som tar ett användarnamn och lösenord och anropar metoden 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;
}
}
Nu när UsersService
klassen är klar, du måste injicera den i din handkontroll. Men först, låt oss prata om att lagra användarnas lösenord säkert.
Den mest kritiska aspekten av registreringsproceduren är användarnas lösenord, som inte får sparas i vanlig text. Det är användarens ansvar att skapa ett starkt lösenord, men det är din skyldighet som utvecklare att hålla sina lösenord säkra. Om ett databasintrång inträffar, skulle användarnas lösenord avslöjas. Och vad händer om det lagras i vanlig text? Jag tror att du vet svaret. För att åtgärda detta, hasha lösenorden med bcrypt.
Så installera bcrypt
och @types/bcrypt
med följande kommando:
npm install @types/bcrypt bcrypt
Med det ur vägen, ställ in din handkontroll. Importera först din UsersService
klass och allt från bcrypt
. Lägg sedan till en konstruktor och en metod som låter dig lägga till en användare; det kommer att hantera inkommande inläggsförfrågningar, kalla det addUser
, med en funktionstext där du ska hasha lösenordet.
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
};
}
}
Registreringen sker i app.module.ts
fil, vilket uppnås genom att lägga till UsersModule
till @Module()
dekoratörens importmatris i 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 {}
Grattis! Du är klar med registreringen. Du kan nu registrera en användare med ett användarnamn och lösenord.
Nu, med registrering ur vägen, lägg till en getUser
funktion till din UsersService
med findOne
metod för att hitta en användare efter användarnamn.
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;
}
}
Skapa autentiseringsmodul
Precis som för användare, skapa en autentiseringsmodul och tjänst specifikt för alla autentiseringar/verifieringar. För att göra det, kör följande kommandon:
nest g module auth
nest g service auth
Ovanstående skapar en auth-mapp, auth.module.ts
och auth.service.ts
, och uppdatera auth.module.ts
och app.module.ts
filer.
Vid det här laget, formen på din applikation src
mappen bör se ut som följer.
└───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
Generera kommandot ovan kommer att uppdatera din app.module.ts
, och det kommer att se ut som kodavsnittet nedan:
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 {}
Autentisera användare
Gå till din auth.module.ts
fil och lägg till UsersModule
i importmatrisen för att möjliggöra åtkomst till UsersService
exporteras från users.module.ts
fil.
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 {}
I din auth.service.ts
fil, ring konstruktorn så att du kan injicera UsersService
, och lägg till en metod för validering som kräver ett användarnamn och lösenord.
För att lägga till några grundläggande valideringar, kontrollera om användaren finns i databasen och jämför det givna lösenordet med det i din databas för att säkerställa att det matchar. Om det finns, returnera användaren i request.user
objekt — annars, returnera 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;
}
}
Gå vidare, skapa en ny fil och döp den till local.strategy.ts
. Den här filen kommer att representera strategin från Passport.js
, som du installerade tidigare, det är den local strategy
. Och inom den, skicka in strategin, som är Strategy
från passport-local
.
Skapa en konstruktor och injicera AuthService
, anropa super()
metod; se till att anropa super()
metod.
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;
}
}
Gå tillbaka till din auth.module.ts
fil. Lägg sedan till PassportModule
till importer och LocalStrategy
till leverantörer.
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 {}
Lägg nu till inloggningsvägen till din 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 när du har alla dessa på plats kan du fortfarande inte logga in en användare eftersom det inte finns något som utlöser inloggningsvägen. Här, använd Guards för att uppnå det.
Skapa en fil och döp den till local.auth.guard.ts
, sedan en klass LocalAuthGuard
som utökar AuthGuard
från NestJS/passport
, där du anger namnet på strategin och skickar in namnet på din strategi, local
.
local.auth.guard.ts.
import { Injectable } from "@nestjs/common"
import { AuthGuard } from "@nestjs/passport"
@Injectable()
export class LocalAuthGuard extends AuthGuard("local") {}
Lägg till UseGuard
dekorator till din inloggningsrutt i users.controller.ts
fil och skicka in 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'};
}
}
Slutligen kan du logga in en användare med ett registrerat användarnamn och lösenord.
Skydda autentiseringsrutter
Du har konfigurerat användarverifiering. Skydda nu dina rutter från obehörig åtkomst genom att begränsa åtkomsten till bara autentiserade användare. Gå till din users.controller.ts
fil och lägg till en annan rutt — namnge den "skyddad" och få den att returnera req.user
objekt.
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;
}
}
Den skyddade rutten i ovanstående kod kommer att returnera ett tomt objekt istället för att returnera användarens uppgifter när en inloggad användare gör en begäran till den eftersom den redan har tappat inloggningen.
För att få det sorterat är det här den sessionsbaserade autentiseringen kommer in.
I sessionsbaserad autentisering, när en användare loggar in, sparas användaren i en session så att varje efterföljande begäran från användaren efter inloggning kommer att hämta informationen från sessionen och ge användaren enkel åtkomst. Sessionen upphör när användaren loggar ut.
För att starta sessionsbaserad autentisering, installera express-session och NestJS-typerna med följande kommando:
npm install express-session @types/express-session
När installationen är klar, gå till din main.ts
fil, roten till din applikation, och gör konfigurationerna där.
Importera allt från passport
och express-session
, lägg sedan till passinitiering och passsession.
Det är att föredra att behålla din hemliga nyckel i dina miljövariabler.
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()
Lägg till en ny fil, authenticated.guard.ts
, i din auth
mapp. Och skapa en ny Guard som kontrollerar om det finns en session för användaren som gör begäran – döp den till 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()
}
}
I ovanstående kod hämtas begäran från sammanhanget och kontrolleras om den är autentiserad. isAuthenticated()
kommer från passport.js
automatiskt; det står. "hej! finns det en session för den här användaren? Fortsätt i så fall."
För att aktivera inloggningen, i din users.controller.ts
fil:
- importera
authenticated
frånauthenticated.guard.ts
; - lägg till
useGuard
dekoratör till denprotected
rutt; och, - passa in
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;
}
}
Vid det här laget misslyckas det fortfarande eftersom du bara har konfigurerat express-session
men implementerade det inte.
När en användare loggar in måste du spara användaren i en session så att användaren kan komma åt andra rutter med sessionen.
En sak att tänka på är att express-session
som standard är biblioteket lagrar sessionen i webbserverns minne.
Innan det går in i sessionen måste du serialisera användaren. När den kommer ut ur sessionen, deserialisera användaren.
Så skapa en ny fil i auth-mappen för serializer och deserializer, döp den till session.serializer.ts
.
Vid det här laget är formen på vår applikation src
mappen ska se ut så här.
└───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)
}
}
Gå tillbaka till din auth.module.ts
fil, ange SessionSerializer
, och lägg till register
metoden till 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
file.
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()
method. 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
Grattis! 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.
Obs! 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.