sql >> Databáze >  >> NoSQL >> MongoDB

NestJS:Jak implementovat autentizaci uživatele na základě relace

Úvod

Je nespornou realitou, že autentizace je kritická v jakékoli aplikaci nebo systému, pokud chcete zabezpečit uživatelská data a umožnit bezpečný přístup k informacím. Autentizace je postup stanovení nebo prokázání, že něco je pravdivé, legitimní nebo platné.

Předpoklady

Tento tutoriál je praktickou ukázkou. Chcete-li pokračovat, ujistěte se, že máte následující:

  • Node.js běžící ve vašem systému, protože NestJS je framework Node.js
  • Nainstalován MongoDB

Co je NestJS?

Nest (NestJS) je aplikační rámec Node.js na straně serveru pro vytváření škálovatelných a efektivních aplikací.

Je napsán v TypeScriptu a postaven na Express, velmi minimalistickém frameworku, který je skvělý sám o sobě, ale postrádá strukturu. Kombinuje programovací paradigmata, jako je objektově orientované programování, funkcionální programování a funkcionální reaktivní programování.

Je to rámec, který můžete použít, pokud chcete na svém backendu hodně struktury. Jeho syntaxe a struktura jsou velmi podobné AngularJS, front-end frameworku. A používá TypeScript, služby a vkládání závislostí stejným způsobem jako AngularJS.

Využívá moduly a řadiče a můžete sestavit řadiče pro soubor pomocí rozhraní příkazového řádku.

Moduly NestJS vám umožňují seskupit související ovladače a poskytovatele služeb do jednoho souboru kódu. Jednoduše řečeno, modul NestJS je soubor TypeScript s @Module anotace (). Tento dekorátor informuje rámec NestJS o tom, které ovladače, poskytovatelé služeb a další přidružené zdroje budou vytvořeny a použity kódem aplikace později.

Co je ověřování na základě relace?

Autentizace založená na relaci je metoda ověřování uživatele, při které server po úspěšném přihlášení vytvoří relaci s ID relace uloženým v souboru cookie nebo místním úložišti ve vašem prohlížeči.

Na základě dalších požadavků je váš soubor cookie ověřen podle ID relace uloženého na serveru. Pokud existuje shoda, je žádost považována za platnou a zpracovanou.

Při používání této metody ověřování je důležité mít na paměti následující doporučené postupy zabezpečení:

  • Generujte dlouhá a náhodná ID relací (128 bitů je doporučená délka), aby byly útoky hrubou silou neúčinné
  • Neuchovávejte žádná citlivá data nebo data specifická pro uživatele
  • Nastavit komunikaci HTTPS jako povinnou pro všechny aplikace založené na relaci
  • Vytvářejte soubory cookie, které mají zabezpečené atributy a atributy pouze HTTP

Proč ověřování na základě relace?

Autentizace na základě relace je bezpečnější než většina metod ověřování, protože je jednoduchá, bezpečná a má omezenou velikost úložiště. Předpokládá se také, že je to nejlepší volba pro webové stránky ve stejné kořenové doméně.

Nastavení projektu

Zahajte nastavení projektu instalací Nest CLI globálně. Pokud již máte nainstalované rozhraní NestJS CLI, nemusíte to dělat.

Nest CLI je nástroj rozhraní příkazového řádku pro nastavení, vývoj a údržbu aplikací Nest.

npm i -g @nestjs/cli

Nyní nastavíme váš projekt spuštěním následujícího příkazu:

nest new session-based-auth

Výše uvedený příkaz vytvoří aplikaci Nest s některými standardy a poté vás vyzve k výběru preferovaného správce balíčků k instalaci požadovaných modulů ke spuštění vaší aplikace. Pro demonstraci tento tutoriál používá npm . Chcete-li pokračovat pomocí npm, stiskněte klávesu Enter .

Pokud vše proběhlo v pořádku, měli byste na svém terminálu vidět výstup podobný tomu na snímku obrazovky níže.

Po dokončení instalace se přesuňte do adresáře projektu a spusťte aplikaci pomocí příkazu níže:

npm run start:dev

Výše uvedený příkaz spustí aplikaci a sleduje změny. Váš projekt src struktura složek by měla vypadat následovně.

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

Instalační závislosti

Nyní, když je vaše aplikace nastavena, pojďme nainstalovat potřebné závislosti.

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

Výše uvedený příkaz nainstaluje Passport.js, oblíbenou ověřovací knihovnu nest.js.

Také nainstalujte typy pro strategii pomocí příkazu níže:

Obsahuje definice typů pro passport-local .

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

Nastavení databáze MongoDB v NestJS

Chcete-li nastavit a připojit databázi, nainstalujte balíček Mongoose a obal NestJS pomocí následujícího příkazu:

npm install --save @nestjs/mongoose mongoose

Obal Mongoose NestJS vám pomáhá používat Mongoose v aplikaci NestJS a poskytuje schválenou podporu TypeScript.

Nyní přejděte na svůj app.module.ts a importujte mongoose modul z @nestjs/mongoose . Poté zavolejte forRoot() metodu, kterou poskytuje modul Mongoose, a předejte řetězec URL vaší databáze.

Nastavení připojení k databázi v app.module.ts pomáhá vaší aplikaci připojit se k databázi okamžitě po spuštění serveru – po spuštění vaší aplikace, protože je to první modul, který se má načíst.

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

Vytvořit modul uživatelů

Kvůli oddělení, aby byl váš kód čistý a dobře organizovaný, vytvořte modul speciálně pro uživatele používající NestJS CLI spuštěním následujícího příkazu:

nest g module users

Výše uvedený příkaz vytvoří users složka s users.module.ts a aktualizuje app.module.ts

Vytvořte také users.service.ts a users.controller.ts soubory s následujícími příkazy:

nest g service users
nest g controller users

Pamatujte, že své složky a soubory můžete vytvářet ručně bez použití vnořeného CLI, ale použití CLI automaticky aktualizuje potřebné složky a usnadňuje vám život.

Vytvořit uživatelské schéma

Dalším krokem je vytvoření uživatelského schématu, ale nejprve přidejte users.model.ts soubor, kde vytvoříte UserSchema

Toto by měl být tvar naší aplikace src složka nyní.

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

Chcete-li vytvořit UserSchema , importujte vše jako mongoose z balíčku mongoose v users.model.ts . Poté zavolejte nové schéma mongoose, návrh modelu uživatele, a předejte objekt JavaScriptu, kde definujete objekt a data uživatele.

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

Vytvořte také rozhraní pro svůj model, které rozšíří mongoose, dokument, který vám pomůže naplnit vaše sbírky MongoDB.

Přejděte na stránku users.module.ts a importujte MongooseModule v poli importů. Poté zavolejte forFeature() metoda poskytovaná MongooseModule a předat pole objektů, které přebírá název a schéma.

To vám umožní sdílet soubor kdekoli pomocí vkládání závislostí.

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

V users.module.ts , exportujte UsersService abyste k němu měli přístup v jiném modulu.

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

Obvykle je dobré zapouzdřit obchodní logiku do samostatné třídy. Taková třída je známá jako služba. Úkolem této třídy je zpracovávat požadavky řadiče a provádět obchodní logiku.

V users.service.ts importujte Model z mongoose , User z users.model.ts a InjectModel od @nestjs/mongoose . Poté přidejte metodu do UsersService třída, která přebírá uživatelské jméno a heslo, a zavolá metodu 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;
  }
}

Nyní, když UsersService třída je připravena, musíte ji vložit do ovladače. Nejprve si ale promluvme o bezpečném ukládání hesel uživatelů.

Nejkritičtějším aspektem procesu registrace jsou hesla uživatelů, která nesmí být uložena v prostém textu. Za vytvoření silného hesla je odpovědný uživatel, ale jako vývojář je vaší povinností udržovat svá hesla v bezpečí. Pokud by došlo k prolomení databáze, byla by prozrazena hesla uživatelů. A co se stane, když je uložen v prostém textu? Věřím, že odpověď znáte. Chcete-li to vyřešit, hašujte hesla pomocí bcrypt.

Nainstalujte tedy bcrypt a @types/bcrypt pomocí následujícího příkazu:

npm install @types/bcrypt bcrypt

Když to bude z cesty, nastavte ovladač. Nejprve importujte UsersService class a vše z bcrypt . Poté přidejte konstruktor a metodu, která vám umožní přidat uživatele; bude zpracovávat příchozí požadavky na příspěvky, nazvěte jej addUser , s tělem funkce, kam heslo zahašujete.

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

Registrace probíhá v app.module.ts souboru, čehož je dosaženo přidáním UsersModule do @Module() pole importů dekoratérů v 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 {}

Gratulujeme! S registrací jste hotovi. Nyní můžete zaregistrovat uživatele pomocí uživatelského jména a hesla.

Nyní, když registrace překáží, přidejte getUser funkce do vaší UsersService pomocí findOne metoda k nalezení uživatele podle uživatelského jména.

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

Vytvořit modul ověřování

Stejně jako pro uživatele vytvořte autentizační modul a službu speciálně pro všechny autentizace/ověření. Chcete-li to provést, spusťte následující příkazy:

nest g module auth
nest g service auth

Výše uvedené vytvoří auth složku, auth.module.ts a auth.service.ts a aktualizujte auth.module.ts a app.module.ts soubory.

V tomto okamžiku je tvar vaší aplikace src složka by měla vypadat následovně.

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

Výše uvedený příkaz generovat aktualizuje váš app.module.ts a bude vypadat jako fragment kódu níže:

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

Ověření uživatelů

Přejděte na svůj auth.module.ts soubor a přidejte UsersModule v poli imports, abyste povolili přístup ke službě UsersService exportován z users.module.ts soubor.

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

Ve vašem auth.service.ts zavolejte konstruktor, abyste mohli vložit UsersService a přidejte metodu ověření, která bude vyžadovat uživatelské jméno a heslo.

Chcete-li přidat některá základní ověření, zkontrolujte, zda uživatel v databázi existuje, a porovnejte dané heslo s heslem ve vaší databázi, abyste se ujistili, že se shoduje. Pokud existuje, vraťte uživatele do request.user object — else, return 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;
      }
    }

Pokračujte dále, vytvořte nový soubor a pojmenujte jej local.strategy.ts . Tento soubor bude představovat strategii z Passport.js , který jste nainstalovali dříve, to je local strategy . A v rámci něj předejte strategii, což je Strategy z passport-local .

Vytvořte konstruktor a vložte AuthService , zavolejte super() metoda; ujistěte se, že voláte super() metoda.

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

Vraťte se na stránku auth.module.ts soubor. Poté přidejte PassportModule k importům a LocalStrategy poskytovatelům.

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

Nyní přidejte cestu přihlášení do 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'};
      }
    }

Nyní, když jste toto všechno zavedli, stále nemůžete přihlásit uživatele, protože neexistuje nic, co by spustilo cestu přihlášení. Zde toho dosáhnete pomocí stráží.

Vytvořte soubor a pojmenujte jej local.auth.guard.ts a poté třídu LocalAuthGuard který rozšiřuje AuthGuard z NestJS/passport , kde uvedete název strategie a předáte název vaší strategie, local .

local.auth.guard.ts.

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

Přidejte UseGuard dekorátor k vaší přihlašovací cestě v users.controller.ts a předejte jej 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'};
      }
    }

Nakonec se můžete přihlásit uživatele pomocí registrovaného uživatelského jména a hesla.

Chránit ověřovací cesty

Úspěšně jste nastavili ověřování uživatele. Nyní chraňte své trasy před neoprávněným přístupem omezením přístupu pouze na ověřené uživatele. Přejděte na stránku users.controller.ts soubor a přidejte další cestu – pojmenujte ji „chráněná“ a nastavte ji na req.user objekt.

users.controller.ts

    import {
      Body,
      Controller,
      Get,
      Post,
      UseGuards,
      Request,
    } from '@nestjs/common';
    import * as bcrypt from 'bcrypt';
    import { LocalAuthGuard } from 'src/auth/local.auth.guard';
    import { UsersService } from './users.service';
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
      //signup
      @Post('/signup')
      async addUser(
        @Body('password') userPassword: string,
        @Body('username') userName: string,
      ) {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(userPassword, saltOrRounds);
        const result = await this.usersService.insertUser(
          userName,
          hashedPassword,
        );
        return {
          msg: 'User successfully registered',
          userId: result.id,
          userName: result.username
        };
      }
      //Post / Login
      @UseGuards(LocalAuthGuard)
      @Post('/login')
      login(@Request() req): any {
        return {User: req.user,
                msg: 'User logged in'};
      }
    // Get / protected
      @Get('/protected')
      getHello(@Request() req): string {
        return req.user;
      }
    }

Chráněná trasa ve výše uvedeném kódu vrátí prázdný objekt namísto vracení podrobností o uživateli, když na něj přihlášený uživatel zadá požadavek, protože již ztratil přihlášení.

Chcete-li to vyřešit, přichází na řadu autentizace založená na relaci.

Při autentizaci na základě relace, když se uživatel přihlásí, je uživatel uložen v relaci, takže jakýkoli další požadavek uživatele po přihlášení získá podrobnosti z relace a poskytne uživateli snadný přístup. Relace vyprší, když se uživatel odhlásí.

Chcete-li spustit ověřování na základě relace, nainstalujte expresní relace a typy NestJS pomocí následujícího příkazu:

npm install express-session @types/express-session

Po dokončení instalace přejděte na main.ts soubor, kořenový adresář vaší aplikace, a tam proveďte konfigurace.

Importujte vše z passport a express-session a poté přidejte inicializaci pasu a relaci pasu.

Je lepší ponechat svůj tajný klíč v proměnných prostředí.

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

Přidejte nový soubor authenticated.guard.ts , ve vašem auth složka. A vytvořte nového strážce, který zkontroluje, zda existuje relace pro uživatele zadávajícího požadavek – pojmenujte ji 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()
  }
}

Ve výše uvedeném kódu je požadavek získán z kontextu a zkontrolován, zda je ověřen. isAuthenticated() pochází z passport.js automaticky; říká. "ahoj! existuje relace pro tohoto uživatele? Pokud ano, pokračujte."

Chcete-li spustit přihlášení, v users.controller.ts soubor:

  • importovat authenticated z authenticated.guard.ts;
  • přidejte useGuard dekoratér na protected trasa; a
  • předejte 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;
      }
    }

V tomto okamžiku stále selhává, protože jste nakonfigurovali pouze express-session ale neimplementoval to.

Když se uživatel přihlásí, musíte jej uložit v relaci, aby měl uživatel přístup k dalším trasám s relací.

Jedna věc, kterou je třeba mít na paměti, je, že ve výchozím nastavení je express-session knihovna ukládá relaci do paměti webového serveru.

Než přejde do relace, musíte uživatele serializovat. Jakmile to vyjde z relace, deserializujte uživatele.

Vytvořte tedy nový soubor ve složce auth pro serializátor a deserializátor, pojmenujte jej session.serializer.ts .

V tomto okamžiku je tvar naší aplikace src složka by měla vypadat takto.

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

Vraťte se na stránku auth.module.ts zadejte SessionSerializer a přidejte register metodu do PassportModule .

auth.module.ts

import { Module } from "@nestjs/common"
import { PassportModule } from "@nestjs/passport"
import { UsersModule } from "src/users/users.module"
import { AuthService } from "./auth.service"
import { LocalStrategy } from "./local.strategy"
import { SessionSerializer } from "./session.serializer"

@Module({
  imports: [UsersModule, PassportModule.register({ session: true })],
  providers: [AuthService, LocalStrategy, SessionSerializer],
})
export class AuthModule {}

Add some codes within the LocalAuthGuard in the local.auth.guard.ts file.

Call the login method in super and pass in the request to trigger the actual login by creating a session. If you want to use sessions, you must remember to trigger the super.login() .

local.auth.guard.ts

    import { ExecutionContext, Injectable } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    @Injectable()
    export class LocalAuthGuard extends AuthGuard('local') {
      async canActivate(context: ExecutionContext) {
        const result = (await super.canActivate(context)) as boolean;
        const request = context.switchToHttp().getRequest();
        await super.logIn(request);
        return result;
      }
    }

If you log in now, you will see the session ID stored in a cookie, which is just a key to the session store, and the cookie gets saved in the browser. The cookie is automatically attached to the rest of the request.

Now that the session is working, you can access the protected route; it will return the expected user’s details.

Logout Users

As mentioned earlier, once a user logs out, you destroy all sessions.

To log out a user, go to the users.controller.ts file, add a logout route, and call the req.session.session() metoda. 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

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

Poznámka: 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. Nelze spustit docker mongo image v systému Windows

  2. Začínáme s Pythonem a MongoDB

  3. Jak programově předem rozdělit Shard Key založený na GUID pomocí MongoDB

  4. Preemptivní zabezpečení s protokolováním auditu pro MongoDB