Autentifikatsiya
Autentifikatsiya aksariyat ilovalarning muhim qismidir. Autentifikatsiyani boshqarish uchun turli yondashuv va strategiyalar mavjud. Har bir loyiha uchun yondashuv uning aniq ilova
Autentifikatsiya aksariyat ilovalarning muhim qismidir. Autentifikatsiyani boshqarish uchun turli yondashuv va strategiyalar mavjud. Har bir loyiha uchun yondashuv uning aniq ilova talablariga bog'liq. Bu bob turli talablar uchun moslashtirilishi mumkin bo'lgan autentifikatsiya yondashuvlarini taqdim etadi.
Keling, talablarimizni aniqlaymiz. Bu use case da klientlar avval foydalanuvchi nomi va parol bilan autentifikatsiya qiladi. Autentifikatsiyadan so'ng, server keyingi so'rovlarda autentifikatsiyani isbotlash uchun authorization headerda bearer token sifatida yuborilishi mumkin bo'lgan JWT ni beradi. Shuningdek, faqat yaroqli JWT bo'lgan so'rovlargina kira oladigan himoyalangan route yaratamiz.
Avval birinchi talabdan boshlaymiz: foydalanuvchini autentifikatsiya qilish. So'ngra JWT chiqarish bilan davom etamiz. Nihoyat, so'rovda yaroqli JWT borligini tekshiradigan himoyalangan route yaratamiz.
Autentifikatsiya modulini yaratish
Avval AuthModule ni va uning ichida AuthService va AuthController ni generatsiya qilamiz. Autentifikatsiya mantiqini AuthService da, autentifikatsiya endpointlarini esa AuthController da amalga oshiramiz.
1$ nest g module auth
2$ nest g controller auth
3$ nest g service authAuthService ni implementatsiya qilarkanmiz, foydalanuvchi operatsiyalarini UsersService ichiga kapsullash foydali bo'ladi, shuning uchun hoziroq o'sha modul va servisni generatsiya qilamiz:
1$ nest g module users
2$ nest g service usersYaratilgan fayllarning default mazmunini quyidagicha almashtiring. Namuna ilovamizda UsersService faqat qattiq kodlangan xotiradagi foydalanuvchilar ro'yxatini va username bo'yicha bitta foydalanuvchini topish metodini ushlab turadi. Haqiqiy ilovada esa bu yerda o'zingiz tanlagan kutubxona (masalan, TypeORM, Sequelize, Mongoose va h.k.) bilan user modelingiz va persistence qatlamingiz bo'ladi.
1import { Injectable } from '@nestjs/common';
2
3// This should be a real class/interface representing a user entity
4export type User = any;
5
6@Injectable()
7export class UsersService {
8 private readonly users = [
9 {
10 userId: 1,
11 username: 'john',
12 password: 'changeme',
13 },
14 {
15 userId: 2,
16 username: 'maria',
17 password: 'guess',
18 },
19 ];
20
21 async findOne(username: string): Promise<User | undefined> {
22 return this.users.find(user => user.username === username);
23 }
24}UsersModule da kerak bo'ladigan yagona o'zgarish - UsersService ni @Module dekoratoridagi exports massiviga qo'shish, shunda u modul tashqarisida ko'rinadigan bo'ladi (buni tez orada AuthService da ishlatamiz).
1import { Module } from '@nestjs/common';
2import { UsersService } from './users.service';
3
4@Module({
5 providers: [UsersService],
6 exports: [UsersService],
7})
8export class UsersModule {}"Sign in" endpointini implementatsiya qilish
AuthService ning vazifasi foydalanuvchini topish va parolni tekshirish. Shu maqsadda signIn() metodini yaratamiz. Quyidagi kodda biz ES6 spread operatoridan foydalanib, foydalanuvchi obyektini qaytarishdan oldin undan password xossasini olib tashlaymiz. Bu foydalanuvchi obyektlarini qaytarishda keng tarqalgan amaliyot, chunki parol yoki boshqa xavfsizlik kalitlari kabi maxfiy maydonlarni oshkor qilmaslik kerak.
1import { Injectable, UnauthorizedException } from '@nestjs/common';
2import { UsersService } from '../users/users.service';
3
4@Injectable()
5export class AuthService {
6 constructor(private usersService: UsersService) {}
7
8 async signIn(username: string, pass: string): Promise<any> {
9 const user = await this.usersService.findOne(username);
10 if (user?.password !== pass) {
11 throw new UnauthorizedException();
12 }
13 const { password, ...result } = user;
14 // TODO: Generate a JWT and return it here
15 // instead of the user object
16 return result;
17 }
18}Albatta, haqiqiy ilovada parolni oddiy matn ko'rinishida saqlamaysiz. Buning o'rniga bcrypt kabi kutubxona va tuzlangan (salted) bir yo'nalishli hash algoritmidan foydalanasiz. Bunday yondashuvda siz faqat hash qilingan parollarni saqlaysiz, so'ng saqlangan parolni kiruvchi parolning hash qilingan nusxasi bilan solishtirasiz, natijada parolni oddiy matn ko'rinishida hech qachon saqlamaysiz yoki oshkor qilmaysiz. Namuna ilovani sodda saqlash uchun bu qat'iy talabni buzib, oddiy matndan foydalanamiz. Buni haqiqiy ilovangizda qilmang!
Endi AuthModule ni UsersModule ni import qiladigan qilib yangilaymiz.
1import { Module } from '@nestjs/common';
2import { AuthService } from './auth.service';
3import { AuthController } from './auth.controller';
4import { UsersModule } from '../users/users.module';
5
6@Module({
7 imports: [UsersModule],
8 providers: [AuthService],
9 controllers: [AuthController],
10})
11export class AuthModule {}Shu bilan, AuthController ni ochib, unga signIn() metodini qo'shamiz. Bu metod klient tomonidan foydalanuvchini autentifikatsiya qilish uchun chaqiriladi. U so'rov bodysida username va parolni oladi va foydalanuvchi autentifikatsiya qilingan bo'lsa JWT token qaytaradi.
1import { Body, Controller, Post, HttpCode, HttpStatus } from '@nestjs/common';
2import { AuthService } from './auth.service';
3
4@Controller('auth')
5export class AuthController {
6 constructor(private authService: AuthService) {}
7
8 @HttpCode(HttpStatus.OK)
9 @Post('login')
10 signIn(@Body() signInDto: Record<string, any>) {
11 return this.authService.signIn(signInDto.username, signInDto.password);
12 }
13}Ideal holatda, Record<string, any> o'rniga so'rov body shaklini belgilovchi DTO klassidan foydalanishimiz kerak. Batafsil ma'lumot uchun validation bobiga qarang.
JWT token
Endi auth tizimimizning JWT qismiga o'tishga tayyormiz. Talablarimizni qayta ko'rib chiqamiz va aniqlashtiramiz:
- Foydalanuvchilarga username/parol bilan autentifikatsiya qilishga ruxsat berish va himoyalangan API endpointlariga keyingi chaqiruvlarda ishlatish uchun JWT qaytarish. Bu talabning katta qismi bajarildi. Uni yakunlash uchun JWT chiqaradigan kodni yozishimiz kerak.
- Yaroqli JWT bearer token mavjudligiga qarab himoyalanadigan API routelarini yaratish
JWT talablarini qo'llab-quvvatlash uchun yana bitta paket o'rnatishimiz kerak:
1$ npm install --save @nestjs/jwt@nestjs/jwt paketi (batafsil bu yerda) JWT bilan ishlashni osonlashtiradigan yordamchi paketdir. Bu JWT tokenlarini yaratish va tekshirishni o'z ichiga oladi.
Servislarimizni aniq modullashgan holda saqlash uchun JWT ni authService da generatsiya qilamiz. auth papkasidagi auth.service.ts faylini oching, JwtService ni inject qiling va signIn metodini quyidagicha JWT token yaratadigan qilib yangilang:
1import { Injectable, UnauthorizedException } from '@nestjs/common';
2import { UsersService } from '../users/users.service';
3import { JwtService } from '@nestjs/jwt';
4
5@Injectable()
6export class AuthService {
7 constructor(
8 private usersService: UsersService,
9 private jwtService: JwtService
10 ) {}
11
12 async signIn(
13 username: string,
14 pass: string,
15 ): Promise<{ access_token: string }> {
16 const user = await this.usersService.findOne(username);
17 if (user?.password !== pass) {
18 throw new UnauthorizedException();
19 }
20 const payload = { sub: user.userId, username: user.username };
21 return {
22 access_token: await this.jwtService.signAsync(payload),
23 };
24 }
25}Biz @nestjs/jwt kutubxonasidan foydalanmoqdamiz; u user obyektining bir qismidan JWT yaratish uchun signAsync() funksiyasini beradi, so'ng biz uni bitta access_token xossasiga ega oddiy obyekt sifatida qaytaramiz. Eslatma: userId qiymatini JWT standartlariga mos ravishda sub xossasida saqlashni tanlaymiz.
Endi AuthModule ni yangi bog'liqliklarni import qiladigan va JwtModule ni sozlaydigan qilib yangilashimiz kerak.
Avval auth papkasida constants.ts yarating va quyidagi kodni qo'shing:
1export const jwtConstants = {
2 secret: 'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.',
3};JWT ni imzolash va tekshirish bosqichlari uchun kalitni shu yerda ulashamiz.
Bu kalitni ommaga oshkor qilmang. Bu yerda kod nimani qilayotganini aniq ko'rsatish uchun shunday qildik, ammo production tizimda bu kalitni himoya qilishingiz kerak, masalan, secrets vault, environment variable yoki konfiguratsiya xizmati orqali.
Endi auth papkasidagi auth.module.ts ni ochib, uni quyidagicha yangilang:
1import { Module } from '@nestjs/common';
2import { AuthService } from './auth.service';
3import { UsersModule } from '../users/users.module';
4import { JwtModule } from '@nestjs/jwt';
5import { AuthController } from './auth.controller';
6import { jwtConstants } from './constants';
7
8@Module({
9 imports: [
10 UsersModule,
11 JwtModule.register({
12 global: true,
13 secret: jwtConstants.secret,
14 signOptions: { expiresIn: '60s' },
15 }),
16 ],
17 providers: [AuthService],
18 controllers: [AuthController],
19 exports: [AuthService],
20})
21export class AuthModule {}JwtModule ni global sifatida ro'yxatdan o'tkazyapmiz, bu ishimizni osonlashtiradi. Bu shuni anglatadiki, ilovaning boshqa joyida JwtModule ni import qilish shart emas.
JwtModule ni register() orqali konfiguratsiya obyektini uzatib sozlaymiz. Nest JwtModule haqida ko'proq bu yerda va mavjud konfiguratsiya opsiyalari haqida ko'proq bu yerda o'qing.
Endi marshrutlarimizni yana cURL bilan sinab ko'ramiz. UsersService da qattiq kodlangan user obyektlaridan birini ishlatib test qilishingiz mumkin.
1$ # POST to /auth/login
2$ curl -X POST http://localhost:3000/auth/login -d '{"username": "john", "password": "changeme"}' -H "Content-Type: application/json"
3{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
4$ # Note: above JWT truncatedAutentifikatsiya guardini implementatsiya qilish
Endi yakuniy talabni bajarishimiz mumkin: endpointlarni himoyalash uchun so'rovda yaroqli JWT bo'lishini talab qilish. Buni routelarimizni himoya qilish uchun ishlatiladigan AuthGuard yaratish orqali qilamiz.
1import {
2 CanActivate,
3 ExecutionContext,
4 Injectable,
5 UnauthorizedException,
6} from '@nestjs/common';
7import { JwtService } from '@nestjs/jwt';
8import { jwtConstants } from './constants';
9import { Request } from 'express';
10
11@Injectable()
12export class AuthGuard implements CanActivate {
13 constructor(private jwtService: JwtService) {}
14
15 async canActivate(context: ExecutionContext): Promise<boolean> {
16 const request = context.switchToHttp().getRequest();
17 const token = this.extractTokenFromHeader(request);
18 if (!token) {
19 throw new UnauthorizedException();
20 }
21 try {
22 const payload = await this.jwtService.verifyAsync(
23 token,
24 {
25 secret: jwtConstants.secret
26 }
27 );
28 // 💡 We're assigning the payload to the request object here
29 // so that we can access it in our route handlers
30 request['user'] = payload;
31 } catch {
32 throw new UnauthorizedException();
33 }
34 return true;
35 }
36
37 private extractTokenFromHeader(request: Request): string | undefined {
38 const [type, token] = request.headers.authorization?.split(' ') ?? [];
39 return type === 'Bearer' ? token : undefined;
40 }
41}Endi himoyalangan route ni implementatsiya qilib, uni himoyalash uchun AuthGuard ni ro'yxatdan o'tkazamiz.
auth.controller.ts faylini ochib, uni quyidagicha yangilang:
1import {
2 Body,
3 Controller,
4 Get,
5 HttpCode,
6 HttpStatus,
7 Post,
8 Request,
9 UseGuards
10} from '@nestjs/common';
11import { AuthGuard } from './auth.guard';
12import { AuthService } from './auth.service';
13
14@Controller('auth')
15export class AuthController {
16 constructor(private authService: AuthService) {}
17
18 @HttpCode(HttpStatus.OK)
19 @Post('login')
20 signIn(@Body() signInDto: Record<string, any>) {
21 return this.authService.signIn(signInDto.username, signInDto.password);
22 }
23
24 @UseGuards(AuthGuard)
25 @Get('profile')
26 getProfile(@Request() req) {
27 return req.user;
28 }
29}Biz hozirgina yaratgan AuthGuard ni GET /profile route ga qo'lladik, shuning uchun u himoyalangan bo'ladi.
Ilova ishga tushganini tekshiring va routelarni cURL bilan sinab ko'ring.
1$ # GET /profile
2$ curl http://localhost:3000/auth/profile
3{"statusCode":401,"message":"Unauthorized"}
4
5$ # POST /auth/login
6$ curl -X POST http://localhost:3000/auth/login -d '{"username": "john", "password": "changeme"}' -H "Content-Type: application/json"
7{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2Vybm..."}
8
9$ # GET /profile using access_token returned from previous step as bearer code
10$ curl http://localhost:3000/auth/profile -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2Vybm..."
11{"sub":1,"username":"john","iat":...,"exp":...}E'tibor bering, AuthModule da JWT ning amal qilish muddati 60 seconds deb sozlangan. Bu juda qisqa muddat, va tokenning muddati tugashi hamda refresh tafsilotlarini ko'rib chiqish bu maqola doirasidan tashqarida. Biroq, biz buni JWTlarning muhim xususiyatini ko'rsatish uchun tanladik. Agar autentifikatsiyadan keyin 60 soniya kutib, GET /auth/profile so'rovini yuborsangiz, 401 Unauthorized javobini olasiz. Buning sababi @nestjs/jwt JWT ning amal qilish vaqtini avtomatik tekshiradi, siz ilovangizda buni qo'lda qilishdan qutulasiz.
Biz endi JWT autentifikatsiya implementatsiyasini yakunladik. JavaScript klientlari (Angular/React/Vue kabi) va boshqa JavaScript ilovalari endi API serverimiz bilan autentifikatsiya qilib, xavfsiz tarzda muloqot qilishi mumkin.
Autentifikatsiyani global yoqish
Agar endpointlaringizning aksariyati default bo'yicha himoyalangan bo'lishi kerak bo'lsa, autentifikatsiya guardini global guard sifatida ro'yxatdan o'tkazishingiz mumkin va har bir controller ustida @UseGuards() dekoratorini qo'yish o'rniga, qaysi routelar public ekanini belgilashingiz mumkin.
Avval AuthGuard ni global guard sifatida quyidagi konstruktsiya bilan ro'yxatdan o'tkazing (istalgan modulda, masalan, AuthModule da):
1providers: [
2 {
3 provide: APP_GUARD,
4 useClass: AuthGuard,
5 },
6],Shu bilan, Nest avtomatik ravishda barcha endpointlarga AuthGuard ni bog'laydi.
Endi routelarni public deb belgilash mexanizmini ta'minlashimiz kerak. Buning uchun SetMetadata dekorator factory funksiyasi yordamida custom dekorator yaratamiz.
1import { SetMetadata } from '@nestjs/common';
2
3export const IS_PUBLIC_KEY = 'isPublic';
4export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);Yuqoridagi faylda ikkita konstantani eksport qildik: biri metadata kalitimiz IS_PUBLIC_KEY, ikkinchisi esa Public deb ataladigan yangi dekoratorimiz (xohlasangiz SkipAuth yoki AllowAnon deb ham nomlashingiz mumkin, loyihangizga mos bo'lsa).
Endi custom @Public() dekoratorimiz bor, uni istalgan metodni bezash uchun quyidagicha ishlatishimiz mumkin:
1@Public()
2@Get()
3findAll() {
4 return [];
5}Nihoyat, AuthGuard "isPublic" metadata topilganda true qaytarishi kerak. Buning uchun Reflector klassidan foydalanamiz (batafsil bu yerda).
1@Injectable()
2export class AuthGuard implements CanActivate {
3 constructor(private jwtService: JwtService, private reflector: Reflector) {}
4
5 async canActivate(context: ExecutionContext): Promise<boolean> {
6 const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
7 context.getHandler(),
8 context.getClass(),
9 ]);
10 if (isPublic) {
11 // 💡 See this condition
12 return true;
13 }
14
15 const request = context.switchToHttp().getRequest();
16 const token = this.extractTokenFromHeader(request);
17 if (!token) {
18 throw new UnauthorizedException();
19 }
20 try {
21 const payload = await this.jwtService.verifyAsync(token, {
22 secret: jwtConstants.secret,
23 });
24 // 💡 We're assigning the payload to the request object here
25 // so that we can access it in our route handlers
26 request['user'] = payload;
27 } catch {
28 throw new UnauthorizedException();
29 }
30 return true;
31 }
32
33 private extractTokenFromHeader(request: Request): string | undefined {
34 const [type, token] = request.headers.authorization?.split(' ') ?? [];
35 return type === 'Bearer' ? token : undefined;
36 }
37}Passport integratsiyasi
Passport node.js uchun eng mashhur autentifikatsiya kutubxonasi bo'lib, hamjamiyat tomonidan yaxshi tanilgan va ko'plab production ilovalarda muvaffaqiyatli qo'llanadi. Bu kutubxonani Nest ilovasiga @nestjs/passport moduli yordamida oson integratsiya qilish mumkin.
Passport ni NestJS bilan qanday integratsiya qilish haqida bu bobda o'qing.
Misol
Ushbu bobdagi kodning to'liq versiyasini bu yerda topishingiz mumkin.