روز 02: اجرای احراز هویت JWT در NestJS با پاسپورت (قسمت 1)

اولین قدم ها
nest g resource auth
این بیشتر از شما برای انتخاب درخواست می کند ❯ REST API
GraphQL (code first)
GraphQL (schema first)
Microservice (non-HTTP)
WebSockets
REST API را انتخاب کنید و با کنترلر و ماژول خدمات dtos کل ماژول را برای شما ایجاد می کند
ثبت نام کاربر
از آنجایی که ما احراز هویت مبتنی بر ایمیل/رمز عبور را به عنوان اولین قدم اجرا می کنیم، کاربر را ثبت می کنیم.
- ابتدا اعتبارسنجی کنید تا مطمئن شوید دادهها مشروع هستند و اعتبار گذرواژه را برای کاهش حملات brute force اضافه کنید.
- سپس برای اطمینان از ایمن بودن استفاده از داده ها، ضدعفونی کنید.
- بررسی کنید که سابقه کاربر از قبل در پایگاه داده وجود داشته باشد، اگر وجود داشته باشد، به این معنی است که کاربر قبلاً یک حساب دارد، بنابراین پاسخی ارسال کنید که این ایمیل قبلاً ثبت شده است.
- اگر بررسی های بالا انجام نشد، به این معنی است که باید یک کاربر ثبت نام کنیم، رمز عبور کاربر را گرفته و آن را با یک کتابخانه هش خوب مانند bcrypt یا argon2 هش کنیم.
- پس از هش کردن، رکورد کاربر را در DB وارد کنید.
- یک ایمیل به کاربر ارسال کنید تا تأیید کنید که ایمیل قانونی است.
- برای جلوگیری از حملات DDoS، محدودیت نرخ را به مسیر اضافه کنید
1 داده های دریافتی را اعتبار سنجی کنید
از آنجایی که nest js دارای یکپارچگی قوی با بسته های اعتبارسنجی توصیه شده مانند کلاس اعتبار سنجی است، اما از تجربه قبلی خود من از zod برای اعتبار سنجی در قسمت های جلویی react js استفاده می کنم و بنابراین من یک ابزار عالی پیدا کردم.
راه حلی برای اکوسیستم nest js به نام nests zod، بنابراین من ترجیح می دهم فعلاً با این اکوسیستم همراه شوم. برای شروع ابتدا کتابخانه را نصب کنیدnpm i nestjs-zod
import { createZodDto } from 'nestjs-zod';
import { z } from 'zod';
const passwordStrengthRegex =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
const registerUserSchema = z
.object({
email: z.string().email(),
password: z
.string()
.min(8)
.regex(
passwordStrengthRegex,
'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character',
),
confirmPassword: z.string().min(8),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match',
});
export class RegisterUserDto extends createZodDto(registerUserSchema) {}
و سپس لوله اعتبار سنجی را در مسیر اعمال کنید
import { Controller, Post, Body, Version, UsePipes } from '@nestjs/common';
import { AuthService } from './auth.service';
import { RegisterUserDto } from './dto/register.dto';
import { ZodValidationPipe } from 'nestjs-zod';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Version('1')
@Post()
@UsePipes(ZodValidationPipe)
async registerUser(@Body() registerUserDto: RegisterUserDto) {
return await this.authService.registerUser(registerUserDto);
}
}
اگر همه ورودی ها را درست ارائه کنیم
بنابراین ما با مرحله اول تمام شده است
بیایید داده ها را پاکسازی کنیم
ما سه ورودی داریم
- رمز عبور: معمولاً گذرواژهها نباید Sanitize شوند، زیرا هرگز برای فرانتاند ارسال و نمایش داده نمیشوند، حتی اگر شخصی یک اسکریپت مخرب به رمز عبور ارسال کند، در نهایت هش میشود.
- confirmPassword: همان داستان بالا
- ایمیل: بله، ایمیلها برای مشتریان ارسال و ارائه میشوند، بنابراین قسمت ایمیل باید Sanitize باشد تا حملات تزریق و اسکریپت را کاهش دهد.
اما ما به صراحت ایمیل اضافه کردیم: z.string().email() که برای این مورد کافی است
اما برای افزودن یک لایه امنیتی اضافی میتوانیم یک لایه بهداشتی اضافه کنیم
import { createZodDto } from 'nestjs-zod';
import { z } from 'zod';
import * as xss from 'xss';
const passwordStrengthRegex =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
const registerUserSchema = z
.object({
email: z.string().transform((input) => xss.filterXSS(input)), // Sanitizing input using xss
password: z
.string()
.min(8)
.regex(
passwordStrengthRegex,
'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character',
),
confirmPassword: z.string().min(8),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match',
});
export class RegisterUserDto extends createZodDto(registerUserSchema) {}
این آزمایشی بود که ما نیز دوباره اضافه کردیم
email: z
.string()
.email()
مرحله 3،4،5
import {
BadRequestException,
Injectable,
InternalServerErrorException,
} from '@nestjs/common';
import { RegisterUserDto } from './dto/register.dto';
import { PrismaService } from 'src/prismaModule/prisma.service';
import * as argon2 from 'argon2';
@Injectable()
export class AuthService {
constructor(private readonly prismaService: PrismaService) {}
async registerUser(registerUserDto: RegisterUserDto) {
// data is validate and sanitized by the registerUserDto
const { email, password } = registerUserDto;
try {
// check if user already exists
const user = await this.prismaService.user.findFirst({
where: {
email,
},
});
if (user) {
throw new BadRequestException('user already eists ');
}
//if use not exists lets hash user password
const hashedPassword = await argon2.hash(registerUserDto.password);
// time to create user
const userData = await this.prismaService.user.create({
data: {
email,
password: hashedPassword,
},
});
if (!userData) {
throw new InternalServerErrorException(
'some thing went wrong while registring user',
);
}
// if user is created successfully then send email to user for email varification
return {
success: true,
message: 'user created successfully',
};
} catch (error) {
throw error;
}
}
}
نکته اصلی که باید به آن توجه کنید من فقط یک پیام موفقیت آمیز بدون هیچ داده مرتبطی برگرداندم
به کاربر مانند شناسه یا ایمیل، زیرا در این مرحله نیازی به ارسال مجدد اطلاعات برای کاربر نیست. پس از ثبت نام، کاربر برای تکمیل جزئیات به صفحه ورود هدایت می شود، بنابراین اجتناب از ارسال داده های غیر ضروری یک عمل امنیتی خوب است.
محدود کردن نرخ
اجرای محدود کردن نرخ در nestjs بسیار آسان است.
برای نصب بسته اجرا شود npm i --save @nestjs/throttler
@Module({
imports: [
ThrottlerModule.forRoot([{
ttl: 60000,
limit: 10,
}]),
],
})
export class AppModule {}
سپس محافظ گاز nestjs را به عنوان محافظ جهانی اضافه کنید
providers: [
AppService,
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
و اینجاست
import { Controller, Post, Body, Version, UsePipes, Req } from '@nestjs/common';
import { AuthService } from './auth.service';
import { RegisterUserDto } from './dto/register.dto';
import { ZodValidationPipe } from 'nestjs-zod';
import { Throttle } from '@nestjs/throttler';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Throttle({
default: {
ttl: 100000, // 1 minute
limit: 5, // 5 requests per minute
},
})
@Version('1')
@Post()
@UsePipes(ZodValidationPipe)
async registerUser(@Body() registerUserDto: RegisterUserDto, @Req() req) {
return await this.authService.registerUser(registerUserDto, req);
}
}
از آنجایی که ثبت نقطه پایانی کاربر یک brute-force نقطه پایانی حساس است
یا حمله به فرهنگ لغت ممکن است رخ دهد ما محدودیت نرخ را سخت نگه داشته ایم
ارسال ایمیل تایید
برای ارسال یک ایمیل تأیید برای استفاده کاربر، ارسال مجدد یک سرویس عالی با استفاده آسان است. اما تصمیم گرفتم یک قسمت جداگانه برای کل سرویس اطلاع رسانی ایجاد کنم تا درک آن برای همه آسان تر شود