MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

NestJS:Как да внедрите потребителско удостоверяване, базирано на сесия

Въведение

Безспорна реалност е, че удостоверяването е от решаващо значение за всяко приложение или система, ако искате да защитите потребителски данни и да разрешите защитен достъп до информация. Удостоверяването е процедура за установяване или демонстриране, че нещо е вярно, легитимно или валидно.

Предварителни условия

Този урок е практическа демонстрация. За да следвате, уверете се, че разполагате със следното:

  • Node.js работи във вашата система, защото NestJS е рамка на Node.js
  • Инсталирана MongoDB

Какво е NestJS?

Nest (NestJS) е рамка за приложения от страна на сървъра Node.js за изграждане на мащабируеми, ефективни приложения.

Той е написан на TypeScript и изграден на Express, много минималистична рамка, която е страхотна сама по себе си, но й липсва структура. Той съчетава парадигми на програмиране като обектно-ориентирано програмиране, функционално програмиране и функционално реактивно програмиране.

Това е рамка, която да използвате, ако искате много структура на вашия бекенд. Синтаксисът и структурата му са много подобни на AngularJS, преден край. И използва TypeScript, услуги и инжектиране на зависимости по същия начин, по който го прави AngularJS.

Той използва модули и контролери и можете да създавате контролери за файл с помощта на интерфейса на командния ред.

Модулите на NestJS ви позволяват да групирате свързани контролери и доставчици на услуги в един кодов файл. Просто казано, модулът NestJS е TypeScript файл с @Module анотация (). Този декоратор информира рамката NestJS за това кои контролери, доставчици на услуги и други свързани ресурси ще бъдат инстанцирани и използвани от кода на приложението по-късно.

Какво е удостоверяване, базирано на сесия?

Удостоверяването, базирано на сесия, е метод за удостоверяване на потребителя, при който сървърът създава сесия след успешно влизане, като идентификационният номер на сесията се съхранява в бисквитка или локално хранилище във вашия браузър.

При последващи заявки, вашата бисквитка се потвърждава спрямо идентификатора на сесията, съхранен на сървъра. Ако има съвпадение, заявката се счита за валидна и обработена.

Когато използвате този метод за удостоверяване, е изключително важно да имате предвид следните най-добри практики за сигурност:

  • Генерирайте дълги и произволни идентификатори на сесии (128 бита е препоръчителната дължина), за да направите атаките с груба сила неефективни
  • Избягвайте да съхранявате чувствителни или специфични за потребителя данни
  • Направете HTTPS комуникациите задължителни за всички базирани на сесии приложения
  • Създайте бисквитки, които имат защитени и само HTTP атрибути

Защо удостоверяване, базирано на сесия?

Удостоверяването, базирано на сесия, е по-сигурно от повечето методи за удостоверяване, защото е просто, сигурно и има ограничен размер за съхранение. Също така се смята, че е най-добрият вариант за уебсайтове в същия основен домейн.

Настройка на проекта

Започнете настройката на проекта си, като инсталирате Nest CLI глобално. Не е необходимо да правите това, ако вече имате инсталиран NestJS CLI.

Nest CLI е инструмент за интерфейс на командния ред за настройка, разработване и поддръжка на Nest приложения.

npm i -g @nestjs/cli

Сега нека настроим вашия проект, като изпълните следната команда:

nest new session-based-auth

Горната команда създава приложение Nest с някои шаблони, след което ви подканва да изберете предпочитания от вас мениджър на пакети, за да инсталирате необходимите модули за стартиране на вашето приложение. За демонстрация този урок използва npm . Натиснете клавиша enter, за да продължите с npm .

Ако всичко е минало добре, трябва да видите изход като този на екранната снимка по-долу на вашия терминал.

След като инсталацията приключи, преместете се в директорията на вашия проект и стартирайте приложението с командата по-долу:

npm run start:dev

Горната команда стартира приложението и следи за промени. Вашият проект src структурата на папките трябва да изглежда по следния начин.

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

Зависимости за инсталиране

След като приложението ви е настроено, нека инсталираме необходимите зависимости.

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

Горната команда инсталира Passport.js, популярна библиотека за удостоверяване на nest.js.

Също така инсталирайте типовете за стратегията с командата по-долу:

Той съдържа дефиниции на типове за passport-local .

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

Настройте база данни MongoDB в NestJS

За да настроите и свържете вашата база данни, инсталирайте пакета Mongoose и NestJS обвивката със следната команда:

npm install --save @nestjs/mongoose mongoose

Обвивката Mongoose NestJS ви помага да използвате Mongoose в приложението NestJS и предоставя одобрена поддръжка на TypeScript.

Сега преминете към вашия app.module.ts , и импортирайте mongoose модул от @nestjs/mongoose . След това извикайте forRoot() method, метод, предоставен от модула Mongoose, и предайте URL низа на вашата база данни.

Настройване на връзката на вашата база данни в 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"

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

Създаване на потребителски модул

За проблеми с разделянето, за да направите кода си чист и добре организиран, създайте модул специално за потребители, използващи NestJS CLI, като изпълните следната команда:

nest g module users

Горната команда създава users папка с users.module.ts и актуализира app.module.ts

Също така създайте users.service.ts и users.controller.ts файлове със следните команди:

nest g service users
nest g controller users

Имайте предвид, че можете да създавате своите папки и файлове ръчно, без да използвате nest CLI, но използването на CLI автоматично актуализира необходимите папки и улеснява живота ви.

Създаване на потребителска схема

Следващата стъпка е да създадете вашата UserSchema, но първо добавете users.model.ts файл, където ще създадете UserSchema

Това трябва да бъде формата на нашето приложение src папка сега.

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

За да създадете UserSchema , импортирайте всичко като mongoose от пакета mongoose в users.model.ts . След това извикайте новата схема на mongoose, план на потребителския модел, и предайте обект на JavaScript, където ще дефинирате потребителския обект и данни.

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

Също така създайте интерфейс за вашия модел, който разширява mongoose, документ, който ви помага да попълвате вашите колекции MongoDB.

Отидете до вашия users.module.ts и импортирайте MongooseModule в масива за импортиране. След това извикайте forFeature() метод, предоставен от MongooseModule и предайте масив от обекти, който приема име и схема.

Това ще ви позволи да споделяте файла навсякъде с помощта на инжектиране на зависимости.

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

В users.module.ts , експортирайте UsersService за да ви даде достъп до него в друг модул.

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

Обикновено е добра идея да се капсулира бизнес логиката в отделен клас. Такъв клас е известен като услуга. Работата на този клас е да обработва заявките на контролера и да изпълнява бизнес логиката.

В users.service.ts файл, импортирайте Model от mongoose , User от users.model.ts и InjectModel от @nestjs/mongoose . След това добавете метод към UsersService клас, който приема потребителско име и парола, и извиква метода 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;
  }
}

Сега, когато UsersService клас е готов, трябва да го инжектирате във вашия контролер. Но първо, нека поговорим за сигурното съхраняване на паролите на потребителите.

Най-критичният аспект на процедурата по регистрация са паролите на потребителите, които не трябва да се записват в обикновен текст. Отговорност на потребителя е да създаде силна парола, но ваше задължение като разработчик е да запазите паролите си защитени. Ако възникне пробив в базата данни, паролите на потребителите ще бъдат разкрити. И какво се случва, ако се съхранява в обикновен текст? Вярвам, че знаеш отговора. За да се справите с това, хеширайте паролите с помощта на bcrypt.

И така, инсталирайте bcrypt и @types/bcrypt със следната команда:

npm install @types/bcrypt bcrypt

Като изключите това, настройте контролера си. Първо импортирайте своя UsersService клас и всичко от bcrypt . След това добавете конструктор и метод, който ви позволява да добавите потребител; той ще обработва входящи заявки за публикуване, наречете го addUser , с тяло на функцията, където ще хеширате паролата.

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

Регистрацията става в app.module.ts файл, което се постига чрез добавяне на UsersModule към @Module() масивът за импортиране на декоратора в 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 {}

Честито! Приключихте с регистрацията. Вече можете да регистрирате потребител с потребителско име и парола.

Сега, като се отстрани регистрацията, добавете getUser функция към вашия UsersService с findOne метод за намиране на потребител по потребителско име.

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

Създаване на модул за удостоверяване

Точно както за потребителите, създайте модул за удостоверяване и услуга специално за всички удостоверения/проверки. За да направите това, изпълнете следните команди:

nest g module auth
nest g service auth

Горното ще създаде папка за удостоверяване, auth.module.ts и auth.service.ts и актуализирайте auth.module.ts и app.module.ts файлове.

В този момент формата на вашето приложение src папката трябва да изглежда по следния начин.

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

Командата за генериране по-горе ще актуализира вашия 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"
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 {}

Удостоверяване на потребители

Отидете на вашия auth.module.ts файл и добавете UsersModule в масива imports, за да разрешите достъп до UsersService експортирани от users.module.ts файл.

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

Във вашия auth.service.ts файл, извикайте конструктора, за да можете да инжектирате UsersService и добавете метод за валидиране, който ще вземе потребителско име и парола.

За да добавите някои основни валидации, проверете дали потребителят съществува в базата данни и сравнете дадената парола с тази във вашата база данни, за да се уверите, че съвпада. Ако съществува, върнете потребителя в request.user обект — иначе, върнете 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;
      }
    }

Продължете по-нататък, създайте нов файл и го наименувайте local.strategy.ts . Този файл ще представлява стратегията от Passport.js , който инсталирахте по-рано, това е local strategy . И в него предайте стратегията, която е Strategy от passport-local .

Създайте конструктор и инжектирайте AuthService , извикайте super() метод; не забравяйте да извикате super() метод.

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

Върнете се към вашия auth.module.ts файл. След това добавете PassportModule към импортиране и LocalStrategy до доставчици.

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

Сега добавете маршрута за влизане към вашия 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'};
      }
    }

Сега, когато разполагате с всичко това, все още не можете да влезете в потребител, защото няма нищо, което да задейства маршрута за влизане. Тук използвайте Guards, за да постигнете това.

Създайте файл и го наименувайте local.auth.guard.ts , след това клас LocalAuthGuard който разширява AuthGuard от NestJS/passport , където ще предоставите името на стратегията и ще въведете името на вашата стратегия, local .

local.auth.guard.ts.

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

Добавете UseGuard декоратор към вашия маршрут за влизане в users.controller.ts файл и предайте 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'};
      }
    }

И накрая, можете да влезете в потребител с регистрирано потребителско име и парола.

Защита на маршрутите за удостоверяване

Успешно настроихте удостоверяване на потребителя. Сега защитете маршрутите си от неоторизиран достъп, като ограничите достъпа само до удостоверени потребители. Отидете на вашия users.controller.ts файл и добавете друг маршрут — именувайте го „защитен“ и го накарайте да връща req.user обект.

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

Защитеният маршрут в горния код ще върне празен обект, вместо да връща данните на потребителя, когато влязъл потребител направи заявка към него, защото вече е загубил данните за вход.

За да се подреди това, тук идва автентификацията, базирана на сесия.

При удостоверяване, базирано на сесия, когато потребител влезе, потребителят се записва в сесия, така че всяка следваща заявка от потребителя след влизане ще вземе подробностите от сесията и ще предостави на потребителя лесен достъп. Сесията изтича, когато потребителят излезе.

За да стартирате базирано на сесия удостоверяване, инсталирайте express-session и типовете NestJS, като използвате следната команда:

npm install express-session @types/express-session

Когато инсталацията приключи, отидете на вашия main.ts файл, корена на вашето приложение и направете конфигурациите там.

Импортирайте всичко от passport и express-session , след това добавете инициализиране на паспорта и паспортна сесия.

За предпочитане е да запазите секретния си ключ в променливите на средата.

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

Добавете нов файл, authenticated.guard.ts , във вашия auth папка. И създайте нов Guard, който проверява дали има сесия за потребителя, който прави заявката - наименувайте го 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()
  }
}

В горния код заявката се получава от контекста и се проверява дали е удостоверена. isAuthenticated() идва от passport.js автоматично; казва. "хей! съществува ли сесия за този потребител? Ако е така, продължете."

За да задействате влизането, във вашия users.controller.ts файл:

  • импорт authenticated от authenticated.guard.ts;
  • добавете useGuard декоратор към protected маршрут; и,
  • предайте 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;
      }
    }

В този момент все още не работи, защото сте конфигурирали само express-session но не го приложи.

Когато потребител влезе, трябва да запазите потребителя в сесия, така че потребителят да има достъп до други маршрути със сесията.

Едно нещо, което трябва да имате предвид е, че по подразбиране express-session библиотеката съхранява сесията в паметта на уеб сървъра.

Преди да влезе в сесията, трябва да сериализирате потребителя. Когато излезе от сесията, десериализирайте потребителя.

Така че, създайте нов файл в папката auth за сериализатор и десериализатор, наименувайте го session.serializer.ts .

В този момент формата на нашето приложение src папката трябва да изглежда така.

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

Върнете се към вашия auth.module.ts файл, осигурете SessionSerializer и добавете register метод към 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 {}

Добавете някои кодове в LocalAuthGuard in the local.auth.guard.ts файл.

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() метод. 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

Честито! 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.

Забележка: 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.


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. 10 въпроса, които да зададете (и да отговорите), когато хоствате MongoDB на AWS

  2. Неуспешно стартиране на MongoDB - ***прекратяване след грешка на fastert().

  3. Има ли лесен начин за експортиране на данните от приложение, разгърнато от метеор?

  4. Mongoose.js създава множество връзки към MongoDB от едно извикване на connect().

  5. Meteor:качване на файл от клиент в колекция Mongo срещу файлова система срещу GridFS