sql >> データベース >  >> NoSQL >> MongoDB

NestJS:セッションベースのユーザー認証を実装する方法

    はじめに

    ユーザーデータを保護し、情報への安全なアクセスを可能にする場合、どのアプリケーションまたはシステムでも認証が重要であることは明白な現実です。認証とは、何かが真実、正当、または有効であることを確立または実証する手順です。

    前提条件

    このチュートリアルは実践的なデモンストレーションです。フォローするには、次のものが揃っていることを確認してください。

    • NestJSはNode.jsフレームワークであるため、システムで実行されているNode.js
    • MongoDBがインストールされています

    NestJSとは何ですか?

    Nest(NestJS)は、スケーラブルで効率的なアプリケーションを構築するためのNode.jsサーバー側アプリケーションフレームワークです。

    TypeScriptで記述され、Express上に構築されています。これは、それ自体は優れていますが、構造が不足している非常に最小限のフレームワークです。オブジェクト指向プログラミング、関数型プログラミング、関数型リアクティブプログラミングなどのプログラミングパラダイムを組み合わせています。

    これは、バックエンドに多くの構造が必要な場合に使用するフレームワークです。その構文と構造は、フロントエンドフレームワークであるAngularJSと非常によく似ています。また、AngularJSと同じように、TypeScript、サービス、依存性注入を使用します。

    モジュールとコントローラーを採用しており、コマンドラインインターフェイスを使用してファイルのコントローラーを作成できます。

    NestJSモジュールを使用すると、関連するコントローラーとサービスプロバイダーを単一のコードファイルにグループ化できます。簡単に言えば、NestJSモジュールは @Moduleを含むTypeScriptファイルです。 注釈()。このデコレータは、NestJSフレームワークに、どのコントローラ、サービスプロバイダー、およびその他の関連リソースがインスタンス化され、後でアプリコードによって使用されるかを通知します。

    セッションベースの認証とは何ですか?

    セッションベースの認証は、ログインに成功した後、サーバーがセッションを作成するユーザー認証の方法であり、セッションIDはCookieまたはブラウザのローカルストレージに保存されます。

    その後のリクエストで、Cookieはサーバーに保存されているセッションIDに対して検証されます。一致するものがある場合、リクエストは有効であると見なされ、処理されます。

    この認証方法を使用するときは、次のセキュリティのベストプラクティスを念頭に置くことが重要です。

    • ブルートフォース攻撃を無効にするために、長くてランダムなセッションID(128ビットが推奨される長さ)を生成します
    • 機密データやユーザー固有のデータの保存は避けてください
    • すべてのセッションベースのアプリでHTTPS通信を必須にする
    • 安全でHTTPのみの属性を持つCookieを作成する

    セッションベースの認証を行う理由

    セッションベースの認証は、シンプルで安全であり、ストレージサイズが制限されているため、ほとんどの認証方法よりも安全です。また、同じルートドメイン内のWebサイトに最適なオプションであると考えられています。

    プロジェクトの設定

    Nest CLIをグローバルにインストールして、プロジェクトのセットアップを開始します。すでにNestJSCLIがインストールされている場合は、これを行う必要はありません。

    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

    上記のコマンドは、人気のあるnest.js認証ライブラリであるPassport.jsをインストールします。

    また、以下のコマンドを使用して戦略のタイプをインストールします。

    passport-localの型定義が含まれています 。

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

    NestJSでMongoDBデータベースを設定する

    データベースをセットアップして接続するには、次のコマンドを使用してMongooseパッケージとNestJSラッパーをインストールします。

    npm install --save @nestjs/mongoose mongoose

    Mongoose NestJSラッパーは、NestJSアプリケーションでMongooseを使用するのに役立ち、承認されたTypeScriptサポートを提供します。

    次に、 app.module.tsにアクセスします。 、 mongooseをインポートします @ nestjs / mongooseのモジュール 。次に、 forRoot()を呼び出します メソッド、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 {}

    ユーザーモジュールの作成

    関心の分離については、コードをクリーンで整理されたものにするために、次のコマンドを実行して、NestJSCLIを使用するユーザー専用のモジュールを作成します。

    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

    ネスト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を作成するには 、 users.model.tsのmongooseパッケージからすべてを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;
    }

    また、MongoDBコレクションの作成に役立つドキュメントであるmongooseを拡張するモデルのインターフェイスを作成します。

    users.module.tsにアクセスしてください MongooseModuleをインポートします imports配列内。次に、 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から 、ユーザー 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からの戦略を表します 、以前にインストールした、それがローカル戦略 。その中で、 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に移動します ファイルを作成し、別のルートを追加します—「protected」という名前を付けて、 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.guard.tsから;
    • useGuardを追加します 保護されたへのデコレータ ルート;そして、
    • 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 のみを構成しているため、失敗します。 しかし、それを実装しませんでした。

    ユーザーがログインするときは、ユーザーがセッションで他のルートにアクセスできるように、ユーザーをセッションに保存する必要があります。

    覚えておくべきことの1つは、デフォルトでは express-session ライブラリはセッションをWebサーバーのメモリに保存します。

    セッションに入る前に、ユーザーをシリアル化する必要があります。セッションが終了したら、ユーザーを逆シリアル化します。

    したがって、シリアライザーとデシリアライザーの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 {}

    Add some codes within the 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. Python + Memcached:分散アプリケーションでの効率的なキャッシング

    2. 在庫管理システムのSQLとNoSQL

    3. Java3.0ドライバーでMongoDB認証を確認する

    4. Redisson、作業キュー/デキュー。システム/ポッドのシャットダウンでの不完全なメッセージ処理に関するメッセージ/要素の処理に関する戦略