MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

NestJS:So implementieren Sie die sitzungsbasierte Benutzerauthentifizierung

Einführung

Es ist eine unbestreitbare Realität, dass die Authentifizierung in jeder Anwendung oder jedem System von entscheidender Bedeutung ist, wenn Sie Benutzerdaten schützen und einen sicheren Zugriff auf Informationen ermöglichen möchten. Authentifizierung ist das Verfahren zur Feststellung oder Demonstration, dass etwas wahr, legitim oder gültig ist.

Voraussetzungen

Dieses Tutorial ist eine praktische Demonstration. Um mitzumachen, stellen Sie sicher, dass Folgendes vorhanden ist:

  • Node.js wird in Ihrem System ausgeführt, weil NestJS ein Node.js-Framework ist
  • MongoDB installiert

Was ist NestJS?

Nest (NestJS) ist ein serverseitiges Node.js-Anwendungsframework zum Erstellen skalierbarer, effizienter Anwendungen.

Es ist in TypeScript geschrieben und basiert auf Express, einem sehr minimalistischen Framework, das für sich genommen großartig ist, aber keine Struktur hat. Es kombiniert Programmierparadigmen wie objektorientierte Programmierung, funktionale Programmierung und funktionale reaktive Programmierung.

Es ist ein Framework, das Sie verwenden können, wenn Sie viel Struktur in Ihrem Backend wünschen. Seine Syntax und Struktur sind AngularJS, einem Frontend-Framework, sehr ähnlich. Und es verwendet TypeScript, Dienste und Dependency Injection auf die gleiche Weise wie AngularJS.

Es verwendet Module und Controller, und Sie können Controller für eine Datei mithilfe der Befehlszeilenschnittstelle erstellen.

Mit NestJS-Modulen können Sie verwandte Controller und Dienstanbieter in einer einzigen Codedatei gruppieren. Einfach ausgedrückt ist ein NestJS-Modul eine TypeScript-Datei mit dem @Module Anmerkung (). Dieser Decorator informiert das NestJS-Framework darüber, welche Controller, Dienstanbieter und andere zugehörige Ressourcen später vom App-Code instanziiert und verwendet werden.

Was ist sitzungsbasierte Authentifizierung?

Sitzungsbasierte Authentifizierung ist eine Methode zur Benutzerauthentifizierung, bei der der Server nach erfolgreicher Anmeldung eine Sitzung erstellt, wobei die Sitzungs-ID in einem Cookie oder lokalen Speicher in Ihrem Browser gespeichert wird.

Bei nachfolgenden Anfragen wird Ihr Cookie anhand der auf dem Server gespeicherten Session-ID validiert. Wenn es eine Übereinstimmung gibt, wird die Anfrage als gültig betrachtet und verarbeitet.

Bei der Verwendung dieser Authentifizierungsmethode ist es wichtig, die folgenden Best Practices für die Sicherheit zu beachten:

  • Generieren Sie lange und zufällige Sitzungs-IDs (128 Bit ist die empfohlene Länge), um Brute-Force-Angriffe unwirksam zu machen
  • Speichern Sie keine sensiblen oder benutzerspezifischen Daten
  • HTTPS-Kommunikation für alle sitzungsbasierten Apps obligatorisch machen
  • Erstellen Sie Cookies mit sicheren und Nur-HTTP-Attributen

Warum sitzungsbasierte Authentifizierung?

Die sitzungsbasierte Authentifizierung ist sicherer als die meisten Authentifizierungsmethoden, da sie einfach und sicher ist und eine begrenzte Speichergröße hat. Es gilt auch als die beste Option für Websites in derselben Root-Domain.

Projekt-Setup

Beginnen Sie mit der Einrichtung Ihres Projekts, indem Sie Nest CLI global installieren. Sie müssen dies nicht tun, wenn Sie bereits NestJS CLI installiert haben.

Die Nest-Befehlszeilenschnittstelle ist ein Befehlszeilenschnittstellentool zum Einrichten, Entwickeln und Verwalten von Nest-Anwendungen.

npm i -g @nestjs/cli

Lassen Sie uns nun Ihr Projekt einrichten, indem Sie den folgenden Befehl ausführen:

nest new session-based-auth

Der obige Befehl erstellt eine Nest-Anwendung mit einigen Standardbausteinen und fordert Sie dann auf, Ihren bevorzugten Paketmanager auszuwählen, um die erforderlichen Module zum Ausführen Ihrer Anwendung zu installieren. Zur Demonstration verwendet dieses Tutorial npm . Drücken Sie die Eingabetaste, um mit npm fortzufahren .

Wenn alles gut gelaufen ist, sollten Sie eine Ausgabe wie die auf dem Screenshot unten auf Ihrem Terminal sehen.

Wechseln Sie nach Abschluss der Installation in Ihr Projektverzeichnis und führen Sie die Anwendung mit dem folgenden Befehl aus:

npm run start:dev

Der obige Befehl führt die Anwendung aus und überwacht auf Änderungen. Ihr Projekt src Ordnerstruktur sollte wie folgt aussehen.

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

Installationsabhängigkeiten

Nachdem Ihre Anwendung nun eingerichtet ist, installieren wir die erforderlichen Abhängigkeiten.

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

Der obige Befehl installiert Passport.js, eine beliebte nest.js-Authentifizierungsbibliothek.

Installieren Sie außerdem die Typen für die Strategie mit dem folgenden Befehl:

Es enthält Typdefinitionen für passport-local .

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

MongoDB-Datenbank in NestJS einrichten

Um Ihre Datenbank einzurichten und zu verbinden, installieren Sie das Mongoose-Paket und den NestJS-Wrapper mit dem folgenden Befehl:

npm install --save @nestjs/mongoose mongoose

Der Mongoose-NestJS-Wrapper hilft Ihnen bei der Verwendung von Mongoose in der NestJS-Anwendung und bietet genehmigte TypeScript-Unterstützung.

Gehen Sie jetzt zu Ihrer app.module.ts , und importieren Sie mongoose Modul von @nestjs/mongoose . Rufen Sie dann forRoot() auf -Methode, eine vom Mongoose-Modul bereitgestellte Methode, und übergeben Sie Ihre Datenbank-URL-Zeichenfolge.

Richten Sie Ihre Datenbankverbindung in app.module.ts ein hilft Ihrer Anwendung, sich sofort mit der Datenbank zu verbinden, wenn der Server startet – nach dem Ausführen Ihrer Anwendung, da es das erste Modul ist, das geladen wird.

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

Benutzermodul erstellen

Um Ihren Code sauber und gut organisiert zu machen, erstellen Sie aus Gründen der Trennung ein Modul speziell für Benutzer, die die NestJS-Befehlszeilenschnittstelle verwenden, indem Sie den folgenden Befehl ausführen:

nest g module users

Der obige Befehl erstellt einen users Ordner mit users.module.ts und aktualisiert app.module.ts

Erstellen Sie außerdem users.service.ts und users.controller.ts Dateien mit den folgenden Befehlen:

nest g service users
nest g controller users

Beachten Sie, dass Sie Ihre Ordner und Dateien manuell erstellen können, ohne die Nest-Befehlszeilenschnittstelle zu verwenden, aber die Verwendung der Befehlszeilenschnittstelle aktualisiert automatisch die erforderlichen Ordner und macht Ihr Leben einfacher.

Benutzerschema erstellen

Der nächste Schritt besteht darin, Ihr Benutzerschema zu erstellen, aber fügen Sie zuerst eine users.model.ts hinzu Datei, in der Sie UserSchema erstellen

Dies sollte die Form unserer Anwendung src sein Ordner jetzt.

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

Zum Erstellen von UserSchema , importieren Sie alles als Mongoose aus dem Mongoose-Paket in users.model.ts . Rufen Sie dann das neue Mongoose-Schema auf, eine Blaupause des Benutzermodells, und übergeben Sie ein JavaScript-Objekt, in dem Sie das Benutzerobjekt und die Daten definieren werden.

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

Erstellen Sie außerdem eine Schnittstelle für Ihr Modell, die Mongoose erweitert, ein Dokument, das Ihnen hilft, Ihre MongoDB-Sammlungen zu füllen.

Gehen Sie zu Ihrer users.module.ts und importiere MongooseModule im imports-Array. Rufen Sie dann forFeature() auf Methode, die von MongooseModule bereitgestellt wird , und übergeben Sie ein Array von Objekten, das Name und Schema akzeptiert.

Dadurch können Sie die Datei mithilfe der Abhängigkeitsinjektion überall freigeben.

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 , exportieren Sie den UsersService damit Sie in einem anderen Modul darauf zugreifen können.

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

Es ist normalerweise eine gute Idee, die Geschäftslogik in einer separaten Klasse zu kapseln. Eine solche Klasse wird als Dienst bezeichnet. Die Aufgabe dieser Klasse besteht darin, die Anforderungen des Controllers zu verarbeiten und die Geschäftslogik auszuführen.

In users.service.ts Datei, importieren Sie Model von mongoose , User aus users.model.ts und InjectModel von @nestjs/mongoose . Fügen Sie dann dem UsersService eine Methode hinzu Klasse, die einen Benutzernamen und ein Passwort entgegennimmt, und ruft die Methode insertUser() auf .

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

Nun, da der UsersService Klasse fertig ist, müssen Sie sie in Ihren Controller einfügen. Aber lassen Sie uns zuerst über das sichere Speichern der Passwörter der Benutzer sprechen.

Der kritischste Aspekt des Registrierungsverfahrens sind die Passwörter der Benutzer, die nicht im Klartext gespeichert werden dürfen. Es liegt in der Verantwortung des Benutzers, ein starkes Passwort zu erstellen, aber es ist Ihre Pflicht als Entwickler, seine Passwörter sicher zu halten. Wenn eine Datenbankverletzung auftritt, würden die Passwörter der Benutzer offengelegt. Und was passiert, wenn es im Klartext gespeichert wird? Ich glaube, Sie kennen die Antwort. Um dies zu beheben, hashen Sie die Passwörter mit bcrypt.

Installieren Sie also bcrypt und @types/bcrypt mit folgendem Befehl:

npm install @types/bcrypt bcrypt

Richten Sie damit Ihren Controller ein. Importieren Sie zuerst Ihren UsersService Klasse und alles von bcrypt . Fügen Sie dann einen Konstruktor und eine Methode hinzu, mit denen Sie einen Benutzer hinzufügen können. Es behandelt eingehende Post-Anfragen, nennen Sie es addUser , mit einem Funktionskörper, in dem Sie das Passwort 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
    };
  }
}

Die Registrierung erfolgt in der app.module.ts Datei, die durch Hinzufügen des UsersModule erreicht wird zum @Module() Import-Array des Dekorateurs 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 {}

Herzliche Glückwünsche! Sie sind mit der Registrierung fertig. Sie können jetzt einen Benutzer mit einem Benutzernamen und einem Passwort registrieren.

Jetzt, nachdem die Registrierung erledigt ist, fügen Sie einen getUser hinzu Funktion zu Ihrem UsersService mit dem findOne Methode, um einen Benutzer anhand des Benutzernamens zu finden.

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

Authentifizierungsmodul erstellen

Erstellen Sie genau wie für Benutzer ein Authentifizierungsmodul und einen Dienst speziell für alle Authentifizierungen/Verifizierungen. Führen Sie dazu die folgenden Befehle aus:

nest g module auth
nest g service auth

Das Obige erstellt einen Authentifizierungsordner, auth.module.ts und auth.service.ts , und aktualisieren Sie auth.module.ts und app.module.ts Dateien.

An dieser Stelle ist die Form Ihrer Anwendung src Ordner sollte wie folgt aussehen.

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

Der obige Generate-Befehl aktualisiert Ihre app.module.ts , und es sieht aus wie das folgende Code-Snippet:

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

Benutzer authentifizieren

Gehen Sie zu Ihrer auth.module.ts Datei und fügen Sie UsersModule hinzu im imports-Array, um den Zugriff auf den UsersService zu ermöglichen exportiert aus users.module.ts Datei.

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 Ihrer auth.service.ts Datei, rufen Sie den Konstruktor auf, damit Sie den UsersService einfügen können , und fügen Sie eine Validierungsmethode hinzu, die einen Benutzernamen und ein Passwort akzeptiert.

Um einige grundlegende Validierungen hinzuzufügen, überprüfen Sie, ob der Benutzer in der Datenbank existiert, und vergleichen Sie das angegebene Passwort mit dem in Ihrer Datenbank, um sicherzustellen, dass es übereinstimmt. Falls vorhanden, geben Sie den Benutzer in request.user zurück Objekt – andernfalls null zurückgeben.

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

Erstellen Sie im weiteren Verlauf eine neue Datei und nennen Sie sie local.strategy.ts . Diese Datei repräsentiert die Strategie aus Passport.js , die Sie zuvor installiert haben, das ist die local strategy . Und übergeben Sie darin die Strategie, die Strategy ist von passport-local .

Erstellen Sie einen Konstruktor und fügen Sie den AuthService ein , rufen Sie super() auf Methode; Stellen Sie sicher, dass Sie super() aufrufen 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;
      }
    }

Gehen Sie zurück zu Ihrer auth.module.ts Datei. Fügen Sie dann PassportModule hinzu zu Importen und LocalStrategy an Anbieter.

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

Fügen Sie nun die Anmelderoute zu Ihrer users.controller.ts hinzu :

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

Nachdem Sie all dies eingerichtet haben, können Sie immer noch keinen Benutzer anmelden, da es nichts gibt, was die Anmelderoute auslösen könnte. Verwenden Sie hier Wachen, um das zu erreichen.

Erstellen Sie eine Datei und nennen Sie sie local.auth.guard.ts , dann eine Klasse LocalAuthGuard das AuthGuard erweitert von NestJS/passport , wo Sie den Namen der Strategie angeben und den Namen Ihrer Strategie, local, übergeben .

local.auth.guard.ts.

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

Fügen Sie UseGuard hinzu decorator zu Ihrer Login-Route in users.controller.ts -Datei und übergeben Sie den 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'};
      }
    }

Schließlich können Sie sich als Benutzer mit einem registrierten Benutzernamen und Passwort anmelden.

Authentifizierungsrouten schützen

Sie haben die Benutzerauthentifizierung erfolgreich eingerichtet. Schützen Sie Ihre Routen jetzt vor unbefugtem Zugriff, indem Sie den Zugriff nur auf authentifizierte Benutzer beschränken. Gehen Sie zu Ihrer users.controller.ts Datei und fügen Sie eine weitere Route hinzu – nennen Sie sie „protected“ und lassen Sie sie den req.user zurückgeben 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;
      }
    }

Die geschützte Route im obigen Code gibt ein leeres Objekt zurück, anstatt die Details des Benutzers zurückzugeben, wenn ein angemeldeter Benutzer eine Anfrage an sie richtet, da er bereits die Anmeldung verloren hat.

Um das zu sortieren, kommt hier die sitzungsbasierte Authentifizierung ins Spiel.

Bei der sitzungsbasierten Authentifizierung wird der Benutzer, wenn er sich anmeldet, in einer Sitzung gespeichert, sodass jede nachfolgende Anfrage des Benutzers nach der Anmeldung die Details aus der Sitzung erfasst und dem Benutzer einfachen Zugriff gewährt. Die Sitzung läuft ab, wenn sich der Benutzer abmeldet.

Um die sitzungsbasierte Authentifizierung zu starten, installieren Sie express-session und die NestJS-Typen mit dem folgenden Befehl:

npm install express-session @types/express-session

Wenn die Installation abgeschlossen ist, gehen Sie zu Ihrer main.ts Datei, das Stammverzeichnis Ihrer Anwendung, und nehmen Sie dort die Konfigurationen vor.

Alles aus passport importieren und express-session , fügen Sie dann Passport Initialize und Passport Session hinzu.

Es ist besser, Ihren geheimen Schlüssel in Ihren Umgebungsvariablen aufzubewahren.

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

Fügen Sie eine neue Datei hinzu, authenticated.guard.ts , in Ihrem auth Mappe. Und erstellen Sie einen neuen Guard, der prüft, ob es eine Sitzung für den Benutzer gibt, der die Anfrage stellt – nennen Sie ihn 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()
  }
}

Im obigen Code wird die Anfrage aus dem Kontext abgerufen und auf Authentifizierung geprüft. isAuthenticated() kommt von passport.js automatisch; es sagt. "Hey! Existiert eine Sitzung für diesen Benutzer? Wenn ja, weitermachen."

Um die Anmeldung auszulösen, in Ihrer users.controller.ts Datei:

  • importiere authenticated von authenticated.guard.ts;
  • fügen Sie den useGuard hinzu decorator zum protected Route; und,
  • übergeben Sie 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;
      }
    }

An diesem Punkt schlägt es immer noch fehl, weil Sie nur express-session konfiguriert haben aber nicht implementiert.

Wenn sich ein Benutzer anmeldet, müssen Sie den Benutzer in einer Sitzung speichern, damit der Benutzer mit der Sitzung auf andere Routen zugreifen kann.

Beachten Sie, dass standardmäßig die express-session Die Bibliothek speichert die Sitzung im Speicher des Webservers.

Bevor es in die Sitzung geht, müssen Sie den Benutzer serialisieren. Wenn es aus der Sitzung kommt, deserialisieren Sie den Benutzer.

Erstellen Sie also eine neue Datei im Authentifizierungsordner für Serializer und Deserializer, nennen Sie sie session.serializer.ts .

An dieser Stelle die Form unserer Anwendung src Ordner sollte so aussehen.

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

Gehen Sie zurück zu Ihrer auth.module.ts Datei, stellen Sie den SessionSerializer bereit , und fügen Sie das register hinzu Methode zum 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 {}

Fügen Sie einige Codes innerhalb des LocalAuthGuard hinzu in der local.auth.guard.ts Datei.

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

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

Herzliche Glückwünsche! 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.

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