MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

NestJS:come implementare l'autenticazione utente basata sulla sessione

Introduzione

È una realtà indiscutibile che l'autenticazione è fondamentale in qualsiasi applicazione o sistema se si desidera proteggere i dati degli utenti e consentire l'accesso protetto alle informazioni. L'autenticazione è la procedura per stabilire o dimostrare che qualcosa è vero, legittimo o valido.

Prerequisiti

Questo tutorial è una dimostrazione pratica. Per seguire, assicurati di avere in atto quanto segue:

  • Node.js in esecuzione nel tuo sistema perché NestJS è un framework Node.js
  • MongoDB installato

Cos'è NestJS?

Nest (NestJS) è un framework applicativo lato server Node.js per la creazione di applicazioni scalabili ed efficienti.

È scritto in TypeScript e costruito su Express, un framework molto minimalista che è ottimo da solo ma manca di struttura. Combina paradigmi di programmazione come la programmazione orientata agli oggetti, la programmazione funzionale e la programmazione reattiva funzionale.

È un framework da usare se vuoi molta struttura sul tuo back-end. La sua sintassi e struttura sono molto simili a AngularJS, un framework front-end. E utilizza TypeScript, servizi e iniezione di dipendenze nello stesso modo in cui fa AngularJS.

Impiega moduli e controller e puoi creare controller per un file utilizzando l'interfaccia della riga di comando.

I moduli NestJS consentono di raggruppare controller e fornitori di servizi correlati in un unico file di codice. In poche parole, un modulo NestJS è un file TypeScript con @Module annotazione (). Questo decoratore informa il framework NestJS su quali controller, fornitori di servizi e altre risorse associate verranno istanziati e utilizzati dal codice dell'app in un secondo momento.

Che cos'è l'autenticazione basata sulla sessione?

L'autenticazione basata sulla sessione è un metodo di autenticazione dell'utente in cui il server crea una sessione dopo un accesso riuscito, con l'ID sessione memorizzato in un cookie o nella memoria locale nel tuo browser.

A richieste successive, il tuo cookie viene convalidato rispetto all'ID di sessione memorizzato sul server. In caso di corrispondenza, la richiesta viene considerata valida ed elaborata.

Quando si utilizza questo metodo di autenticazione, è fondamentale tenere a mente le seguenti best practice di sicurezza:

  • Genera ID di sessione lunghi e casuali (128 bit è la lunghezza consigliata) per rendere inefficaci gli attacchi di forza bruta
  • Evita di archiviare dati sensibili o specifici dell'utente
  • Rendi obbligatorie le comunicazioni HTTPS per tutte le app basate su sessioni
  • Crea cookie con attributi sicuri e solo HTTP

Perché l'autenticazione basata sulla sessione?

L'autenticazione basata sulla sessione è più sicura della maggior parte dei metodi di autenticazione perché è semplice, sicura e ha una dimensione di archiviazione limitata. Si ritiene inoltre che sia l'opzione migliore per i siti Web nello stesso dominio principale.

Impostazione progetto

Inizia la configurazione del tuo progetto installando Nest CLI a livello globale. Non è necessario farlo se hai già installato NestJS CLI.

Nest CLI è uno strumento di interfaccia a riga di comando per la configurazione, lo sviluppo e la manutenzione delle applicazioni Nest.

npm i -g @nestjs/cli

Ora, impostiamo il tuo progetto eseguendo il seguente comando:

nest new session-based-auth

Il comando precedente crea un'applicazione Nest con alcuni standard, quindi ti chiede di scegliere il tuo gestore di pacchetti preferito per installare i moduli richiesti per eseguire la tua applicazione. A scopo dimostrativo, questo tutorial utilizza npm . Premi il tasto Invio per continuare con npm .

Se tutto è andato bene, dovresti vedere un output come quello nello screenshot qui sotto sul tuo terminale.

Una volta completata l'installazione, spostati nella directory del tuo progetto ed esegui l'applicazione con il comando seguente:

npm run start:dev

Il comando precedente esegue l'applicazione e controlla le modifiche. Il tuo progetto src la struttura delle cartelle dovrebbe apparire come segue.

└───src
│   └───app.controller.ts
│   └───app.modules.ts
│   └───app.service.ts
│   └───main.ts

Installa dipendenze

Ora che la tua applicazione è configurata, installiamo le dipendenze necessarie.

npm install --save @nestjs/passport passport passport-local

Il comando precedente installa Passport.js, una popolare libreria di autenticazione nest.js.

Inoltre, installa i tipi per la strategia con il comando seguente:

Contiene le definizioni di tipo per passport-local .

npm install --save-dev @types/passport-local

Configura il database MongoDB in NestJS

Per configurare e connettere il tuo database, installa il pacchetto Mongoose e il wrapper NestJS con il seguente comando:

npm install --save @nestjs/mongoose mongoose

Il wrapper Mongoose NestJS ti aiuta a utilizzare Mongoose nell'applicazione NestJS e fornisce supporto TypeScript approvato.

Ora vai al tuo app.module.ts e importa la mongoose modulo da @nestjs/mongoose . Quindi chiama forRoot() metodo, un metodo fornito dal modulo Mongoose, e passa la stringa URL del database.

Configurazione della connessione al database in app.module.ts aiuta la tua applicazione a connettersi al database immediatamente all'avvio del server, dopo aver eseguito l'applicazione poiché è il primo modulo ad essere caricato.

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 {}

Crea modulo utenti

Per problemi di separazione, per rendere il tuo codice pulito e ben organizzato, crea un modulo specifico per gli utenti che utilizzano NestJS CLI eseguendo il comando seguente:

nest g module users

Il comando precedente crea un users cartella con users.module.ts e aggiorna app.module.ts

Inoltre, crea users.service.ts e users.controller.ts file con i seguenti comandi:

nest g service users
nest g controller users

Tieni presente che puoi creare cartelle e file manualmente senza utilizzare la CLI nest, ma l'utilizzo della CLI aggiorna automaticamente le cartelle necessarie e ti semplifica la vita.

Crea schema utente

Il passaggio successivo consiste nel creare il tuo UserSchema, ma prima aggiungi un users.model.ts file, dove creerai UserSchema

Questa dovrebbe essere la forma della nostra applicazione src cartella ora.

└───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

Per creare UserSchema , importa tutto come mangusta dal pacchetto mangusta in users.model.ts . Quindi chiama il nuovo schema mongoose, un progetto del modello utente, e passa un oggetto JavaScript in cui definirai l'oggetto utente e i dati.

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;
}

Inoltre, crea un'interfaccia per il tuo modello che estenda mongoose, un documento che ti aiuta a popolare le tue raccolte MongoDB.

Vai al tuo users.module.ts e importa MongooseModule nell'array imports. Quindi chiama forFeature() metodo fornito da MongooseModule e passa un array di oggetti che accetta nome e schema.

Ciò ti consentirà di condividere il file ovunque con l'aiuto dell'iniezione delle dipendenze.

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 , esporta il UsersService per consentirti di accedervi in ​​un altro modulo.

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 {}

Di solito è una buona idea incapsulare la logica aziendale in una classe separata. Tale classe è nota come servizio. Il compito di questa classe è elaborare le richieste del controller ed eseguire la logica aziendale.

In users.service.ts file, importa Model da mongoose , User da users.model.ts e InjectModel da @nestjs/mongoose . Quindi aggiungi un metodo a UsersService classe che accetta un nome utente e una password e chiama il metodo 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;
  }
}

Ora che il UsersService la classe è pronta, devi iniettarla nel tuo controller. Ma prima, parliamo della memorizzazione sicura delle password degli utenti.

L'aspetto più critico della procedura di registrazione sono le password degli utenti, che non devono essere salvate in chiaro. È responsabilità dell'utente creare una password complessa, ma è tuo obbligo come sviluppatore mantenere le proprie password al sicuro. Se si verifica una violazione del database, le password degli utenti verrebbero esposte. E cosa succede se viene archiviato in testo normale? Credo tu sappia la risposta. Per risolvere questo problema, esegui l'hashing delle password utilizzando bcrypt.

Quindi, installa bcrypt e @types/bcrypt con il seguente comando:

npm install @types/bcrypt bcrypt

Detto questo, configura il controller. Innanzitutto, importa il tuo UsersService class e tutto da bcrypt . Quindi aggiungi un costruttore e un metodo che ti permetta di aggiungere un utente; gestirà le richieste di posta in arrivo, chiamalo addUser , con un corpo di funzione in cui eseguirai l'hashing della password.

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
    };
  }
}

La registrazione avviene nel app.module.ts file, che si ottiene aggiungendo il UsersModule al @Module() l'array delle importazioni del decoratore 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 {}

Congratulazioni! Hai finito con la registrazione. Ora puoi registrare un utente con un nome utente e una password.

Ora, con la registrazione fuori mano, aggiungi un getUser funzione al tuo UsersService con il findOne metodo per trovare un utente per nome utente.

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;
  }
}

Crea modulo di autenticazione

Proprio come per gli utenti, crea un modulo di autenticazione e un servizio specifico per tutte le autenticazioni/verificazioni. Per farlo, esegui i seguenti comandi:

nest g module auth
nest g service auth

Quanto sopra creerà una cartella di autenticazione, auth.module.ts e auth.service.ts e aggiorna auth.module.ts e app.module.ts file.

A questo punto, la forma della tua applicazione src la cartella dovrebbe apparire come segue.

└───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

Il comando generate sopra aggiornerà il tuo app.module.ts e sarà simile allo snippet di codice di seguito:

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 {}

Autentica utenti

Vai al tuo auth.module.ts file e aggiungi UsersModule nell'array imports per consentire l'accesso a UsersService esportato da users.module.ts file.

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 {}

Nel tuo auth.service.ts file, chiama il costruttore in modo da poter inserire il UsersService e aggiungi un metodo per la convalida che richiede un nome utente e una password.

Per aggiungere alcune convalide di base, controlla se l'utente esiste nel database e confronta la password fornita con quella nel tuo database per assicurarti che corrisponda. Se esiste, restituisci l'utente nel request.user oggetto — altrimenti, restituisce 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;
      }
    }

Andando oltre, crea un nuovo file e chiamalo local.strategy.ts . Questo file rappresenterà la strategia di Passport.js , che hai installato in precedenza, ovvero la local strategy . E al suo interno, passa la strategia, che è la Strategy da passport-local .

Crea un costruttore e inserisci AuthService , chiama il super() metodo; assicurati di chiamare super() metodo.

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;
      }
    }

Torna al tuo auth.module.ts file. Quindi aggiungi PassportModule per importare e LocalStrategy ai fornitori.

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 {}

Ora aggiungi il percorso di accesso al tuo 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'};
      }
    }

Ora che hai impostato tutti questi elementi, non puoi ancora accedere a un utente perché non c'è nulla per attivare il percorso di accesso. Usa le Guardie per ottenerlo.

Crea un file e chiamalo local.auth.guard.ts , quindi una classe LocalAuthGuard che estende AuthGuard da NestJS/passport , dove fornirai il nome della strategia e passerai il nome della tua strategia, local .

local.auth.guard.ts.

import { Injectable } from "@nestjs/common"
import { AuthGuard } from "@nestjs/passport"
@Injectable()
export class LocalAuthGuard extends AuthGuard("local") {}

Aggiungi il UseGuard decorator al percorso di accesso in users.controller.ts file e passare il 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'};
      }
    }

Infine, puoi accedere a un utente con un nome utente e una password registrati.

Proteggi percorsi di autenticazione

Hai impostato correttamente l'autenticazione utente. Ora proteggi i tuoi percorsi dall'accesso non autorizzato limitando l'accesso ai soli utenti autenticati. Vai al tuo users.controller.ts file e aggiungi un altro percorso:chiamalo "protetto" e fallo restituire il req.user oggetto.

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;
      }
    }

Il percorso protetto nel codice precedente restituirà un oggetto vuoto invece di restituire i dettagli dell'utente quando un utente che ha effettuato l'accesso gli fa una richiesta perché ha già perso l'accesso.

Per metterlo in ordine, è qui che entra in gioco l'autenticazione basata sulla sessione.

Nell'autenticazione basata sulla sessione, quando un utente esegue l'accesso, l'utente viene salvato in una sessione in modo che qualsiasi richiesta successiva dell'utente dopo l'accesso acquisirà i dettagli dalla sessione e garantirà all'utente un facile accesso. La sessione scade quando l'utente si disconnette.

Per avviare l'autenticazione basata sulla sessione, installa express-session e i tipi NestJS utilizzando il comando seguente:

npm install express-session @types/express-session

Al termine dell'installazione, vai al tuo main.ts file, la radice della tua applicazione ed esegui lì le configurazioni.

Importa tutto da passport e express-session , quindi aggiungi l'inizializzazione del passaporto e la sessione del passaporto.

È preferibile mantenere la chiave segreta nelle variabili di ambiente.

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()

Aggiungi un nuovo file, authenticated.guard.ts , nella tua auth cartella. E crea un nuovo Guard che controlli se c'è una sessione per l'utente che effettua la richiesta — chiamalo 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()
  }
}

Nel codice sopra, la richiesta viene ottenuta dal contesto e verificata se autenticata. isAuthenticated() proviene da passport.js automaticamente; dice. "ehi! esiste una sessione per questo utente? Se sì, continua."

Per attivare l'accesso, nel tuo users.controller.ts file:

  • importa authenticated da authenticated.guard.ts;
  • aggiungi useGuard decoratore al protected rotta; e,
  • passa 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;
      }
    }

A questo punto, non riesce ancora perché hai solo configurato express-session ma non l'ha implementato.

Quando un utente effettua l'accesso, è necessario salvare l'utente in una sessione in modo che l'utente possa accedere ad altri percorsi con la sessione.

Una cosa da tenere a mente è che per impostazione predefinita, la express-session library memorizza la sessione nella memoria del server web.

Prima che entri nella sessione, è necessario serializzare l'utente. Quando esce dalla sessione, deserializzare l'utente.

Quindi, crea un nuovo file nella cartella auth per serializer e deserializer, chiamalo session.serializer.ts .

A questo punto, la forma della nostra applicazione src la cartella dovrebbe assomigliare a questa.

    └───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)
  }
}

Torna al tuo auth.module.ts file, fornisci il SessionSerializer e aggiungi il register metodo al 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 {}

Aggiungi alcuni codici all'interno di 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() metodo. 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

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

Congratulazioni! 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.

Nota: 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.