JWT аутентификация в NestJS

Аутентификация является одним из наиболее важных аспектов любого приложения. Это повышает безопасность приложения, проверяя пользователей перед предоставлением им доступа к различным частям приложения. Аутентификация также позволяет компаниям отслеживать, сколько людей используют их продукты. В этом руководстве будет продемонстрирован пошаговый процесс реализации аутентификации пользователя JWT в NestJS.

Предпосылки

Этот урок представляет собой практическую демонстрацию. Чтобы продолжить, убедитесь, что у вас установлено следующее:

  • Node.js v14 и выше
  • MongoDB
  • Yarn установлен ​​глобально; использовать командуnpm install --global yarn

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

Чтобы настроить проект, вам сначала необходимо установить Nest CLI глобально с помощью следующей команды:

npm i - g @nestjs / cli

После завершения установки создайте новый проект, например:

nest new auth-with-nest

Далее вам будет предложено выбрать менеджер пакетов для установки зависимостей. Для этой демонстрации мы будем использовать Yarn.

Терминальная пряжа

Выберите yarn и нажмите клавишу Enter . Теперь подождите, пока Yarn установит все необходимые зависимости, необходимые для запуска приложения.

Настройка базы данных MongoDB

Чтобы настроить и подключить базу данных, установите пакет Mongoose, bcrypt и оболочку NestJS с помощью следующей команды:

npm install -- сохранить @nestjs / mongoose @types / bcrypt mongoose bcrypt

Теперь обновите файл app.module.ts и настройте Mongoose следующим образом:

import { MongooseModule } from '@nestjs/mongoose';
@Module({
  imports: [MongooseModule.forRoot('mongodb://localhost/authentication')],
})

В приведенном выше фрагменте мы импортировали MongooseModule в корень AppModule.

Создание пользовательского модуля

Чтобы ваш код был чистым и хорошо организованным, создайте модуль специально для пользователей NestJS CLI, выполнив следующую команду:

nest g module users

Приведенный выше код создает пользовательскую папку users с файлом module.ts и файлом обновлений pp.module.ts.

Создание пользовательской схемы

Чтобы создать пользовательскую схему, создайте файл users.model.ts в папке src/users и добавьте следующий код:

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type UserDocument = User & Document;

@Schema()
export class User {
  @Prop()
  username: string;

  @Prop()
  password: string;
}

export const UserSchema = SchemaFactory.createForClass(User);

Здесь мы определили форму нашей User схемы с помощью @Schema() декоратора и @Prop() декоратора.

Mongoose сопоставит схему с коллекцией MongoDB. Схема определяет форму документов коллекции.

Теперь замените код в файле user/user.module.ts и сделайте доступным в импорте userSchema следующий код:

import { Module } from '@nestjs/common';
import { UsersService } from './user.service';
import { UsersController } from './user.controller';
import { MongooseModule } from "@nestjs/mongoose"
import { UserSchema } from "./user.model"

@Module({
  imports: [MongooseModule.forFeature([{ name: "user", schema: UserSchema }])],
  providers: [UsersService],
  controllers: [UsersController]
})
export class UserModule {}

Создание пользовательского сервиса

Создав пользовательскую схему, выполните приведенную ниже команду, чтобы создать пользовательскую службу:

nest g module users

Этот код создает файл users.service.ts и обновляет файл app.module.ts.

NB, вы можете создавать свои файлы и папки вручную, но интерфейс командной строки NestJS облегчит вам жизнь, автоматически обновляя необходимые папки.

Теперь добавьте в файл users.service.ts следующий код:

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, UserDocument } from './users.model';

@Injectable()
export class UsersService {
    constructor(@InjectModel('user') private readonly userModel: Model<UserDocument>) { }
    async createUser(username: string, password: string): Promise<User> {
        return this.userModel.create({
            username,
            password,
        });
    }
    async getUser(query: object ): Promise<User> {
        return this.userModel.findOne(query);
    }
}

Здесь мы использовали @InjectModel() декоратор для внедрения userModel в файл UsersService.

Создание пользовательского контроллера

Теперь давайте создадим пользовательский контроллер для определения маршрутов API:

nest g service users

Добавьте код в файл users.controller.ts:

import { Body, Controller, Post, Get, Param } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from './users.model';
import * as bcrypt from 'bcrypt';

@Controller('auth')
export class UsersController {
    constructor(private readonly usersService: UsersService) { }

    @Post('/signup')
    async createUser(
        @Body('password') password: string,
        @Body('username') username: string,
    ): Promise<User> {
        const saltOrRounds = 10;
        const hashedPassword = await bcrypt.hash(password, saltOrRounds);
        const result = await this.usersService.createUser(
            username,
            hashedPassword,
        );
        return result;
    }
}

Здесь мы определили два маршрута API и использовали созданные нами сервисы.

Создание модуля авторизации

Давайте начнем с создания модуля аутентификации, например:

nest g module auth

Эта команда создаст новую папку auth с файлом auth.module.ts; он также обновит файл app.module.ts.

Настройка JWT

Теперь давайте реализуем веб-токен JSON для аутентификации пользователей в приложении.

Для начала установите следующие зависимости:

npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt

Затем создайте новый файл local.auth.ts и добавьте следующий код:

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super();
  }

  async validate(username: string, password: string): Promise<any> {
    const user = await this.authService.validateUser(username, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

Здесь мы реализовали локальную стратегию паспорта для аутентификации веб-токена JSON. По умолчанию стратегия «Password-local» ожидает username и password свойства в теле запроса.

Мы также реализовали метод validate(), который промежуточное ПО Passport будет вызывать для проверки пользователя с использованием соответствующего набора параметров, зависящего от стратегии.

Затем замените код AuthModule на следующий:

import { Module } from "@nestjs/common"
import { UserModule } from "src/user/user.module";
import { AuthService } from "./auth.service"
import { PassportModule } from "@nestjs/passport"
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { UsersService } from "src/user/user.service";
import { MongooseModule } from "@nestjs/mongoose"
import { UserSchema } from "../user/user.model"
import { LocalStrategy } from './local-strategy';


@Module({
  imports: [UserModule, PassportModule, JwtModule.register({
    secret: 'secretKey',
    signOptions: { expiresIn: '60s' },
  }), MongooseModule.forFeature([{ name: "user", schema: UserSchema }])],
  providers: [AuthService, UsersService, LocalStrategy],
  controllers: [AuthController],
})
export class AuthModule { }

Здесь мы импортировали PassportModule и JwtModuleв массив импорта. Затем мы использовали этот register метод для регистрации JWT, указав секрет и срок действия.

Мы также сделали UserSchemaдоступными в импорте и добавили UserService наши и LocalStrategyв массив провайдеров.

из соображений безопасности всегда сохраняйте свой секрет JWT в переменной среды.

Создание службы аутентификации и контроллера

Теперь давайте добавим в приложение функции аутентификации.

С настроенными JWT и Passport выполните следующую команду, чтобы создать auth.service.ts и auth.controller.ts файлы в папке auth:

nest generate service auth
nest generate controller auth

Затем откройте файл auth/auth.service.ts и аутентифицируйте пользователей с помощью следующего кода:

import { Injectable, NotAcceptableException } from '@nestjs/common';
import { UsersService } from 'src/user/user.service';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
    constructor(private readonly usersService: UsersService, private jwtService: JwtService) { }
    async validateUser(username: string, password: string): Promise<any> {
        const user = await this.usersService.getUser({ username });
        if (!user) return null;
        const passwordValid = await bcrypt.compare(password, user.password)
        if (!user) {
            throw new NotAcceptableException('could not find the user');
        }
        if (user && passwordValid) {
            return user;
        }
        return null;
    }
    async login(user: any) {
        const payload = { username: user.username, sub: user._id };
        return {
            access_token: this.jwtService.sign(payload),
        };
    }
}

Здесь мы создали validateUser метод, чтобы проверить, соответствует ли пользователь из записи пользователя из базы данных. Если совпадения нет, метод возвращает значение.user.modelnull

Мы также создали loginметод, который использует этот метод для создания токена доступа JWT для возвращенного пользователя из нашего файла .jwtService.signvalidateLocalStrategy

Теперь добавьте фрагмент кода ниже в файл auth/auth.controller.ts, чтобы создать маршрут login для пользователя .

import { Controller, Request, Post, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthGuard } from '@nestjs/passport';

@Controller()
export class AuthController {
    constructor(private authService: AuthService) { }

    @UseGuards(AuthGuard('local'))
    @Post('auth/login')
    async login(@Request() req) {
        return this.authService.login(req.user);
    }
}

Здесь мы использовали @UseGuards() декоратор для обеспечения аутентификации, когда пользователь запрашивает маршрут входа. С помощью класса AuthGuardlocal мы можем аутентифицировать пользователя, используя стратегию.

Тестирование приложения

Теперь давайте протестируем приложение с помощью Postman. Мы начнем с signup route.

Сначала запустите приложение:

npm run start

Затем откройте Postman и проверьте маршрут регистрации localhost:3000/users/signup, отправив почтовый запрос на конечную точку.

Маршрут регистрации почтальона

Теперь проверьте конечную точку входа в систему, отправив почтовый запрос на конечную точку localhost:3000/auth/login .

Конечная точка пост-запроса

Если username и passwordсуществуют в базе данных, пользователь получит, access_tokenкак показано выше. С помощью access_tokenпользователь сможет получить доступ к защищенным маршрутам в API.

Заключение

В этом руководстве мы представили обзор NestJS, а затем продемонстрировали, как реализовать аутентификацию пользователя JWT в NestJS API.