Guards — Authorization, Role-based

~25 daq · NestJS · Boshlang'ich daraja

Guard nima?

Tasavvur qiling: siz katta mehmonxonaga keldingiz. Kirish eshigidan o'tdingiz — bu Middleware. Lift oldida xavfsizlik xodimi turibdi. U sizning kartangizni tekshiradi — 5-qavatga ruxsatingiz bormi? Bor bo'lsa — o'tkazadi. Yo'q bo'lsa — o'tkazmaydi. Bu xavfsizlik xodimi — Guard.

Middleware va Guard ning farqi shunda:

Middleware — so'rov kimdan kelayotganini bilmaydi. U faqat so'rovni ko'radi, loglaydi, header qo'shadi. "Bu foydalanuvchi kim?" — bu savol uni qiziqtirmaydi.

Guard — aynan shu savolga javob beradi. "Bu foydalanuvchi kim? Uning bu sahifaga kirish huquqi bormi?" Guard true qaytarsa — so'rov davom etadi. false qaytarsa — 403 Forbidden xatosi qaytadi.


Guard qanday ishlaydi?

Guard CanActivate interfeysini implement qiladi. Bu interfeys bitta metod talab qiladi — canActivate. Bu metod true yoki false qaytaradi.

// common/guards/auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    // HTTP so'rovini olamiz
    const request = context.switchToHttp().getRequest();

    // Header dan token ni olamiz
    const token = request.headers["authorization"];

    // Token bo'lsa — o'tkazamiz, bo'lmasa — to'xtatamiz
    return !!token;
  }
}

ExecutionContext — bu Guard ning ko'zi. U orqali so'rov haqida hamma narsani bilasiz: HTTP so'rovimi, WebSocket mi, gRPC mi — hammasiga kirish imkoni bor.

context.switchToHttp().getRequest() — HTTP so'rovini olish. Middleware dagidek req ob'ekti — header lar, body, foydalanuvchi ma'lumotlari hammasi shu yerda.

!!token — token mavjud bo'lsa true, bo'lmasa false. Ikki ! belgisi har qanday qiymatni boolean ga aylantiradi.


Guard ni ulash

Guard uchta joyda ishlatilishi mumkin — global, controller darajasida, metod darajasida.

Metod darajasida — faqat shu endpoint himoyalanadi:

// users/users.controller.ts
import { Controller, Get, UseGuards } from "@nestjs/common";
import { AuthGuard } from "../common/guards/auth.guard";

@Controller("users")
export class UsersController {
  @Get()
  @UseGuards(AuthGuard) // faqat shu metod himoyalangan
  findAll() {
    return "Faqat login qilganlar ko'ra oladi";
  }

  @Get("public")
  publicEndpoint() {
    return "Hamma ko'ra oladi"; // Guard yo'q
  }
}

Controller darajasida — controllerdagi barcha metodlar himoyalanadi:

@Controller('users')
@UseGuards(AuthGuard)  // barcha metodlar himoyalangan
export class UsersController {

  @Get()
  findAll() { ... }

  @Post()
  create() { ... }
}

Global darajasida — butun ilova himoyalanadi:

// main.ts
import { AuthGuard } from "./common/guards/auth.guard";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalGuards(new AuthGuard());
  await app.listen(3000);
}

Global Guard qo'yilganda — barcha endpointlar himoyalanadi. Bu ba'zan muammo — login va ro'yxatdan o'tish endpointlari ham himoyalanib qoladi. Ularni ochiq qoldirish uchun keyingi bo'limda @Public() decorator ni ko'ramiz.


JWT Guard — real hayotdagi misol

Haqiqiy loyihalarda Guard JWT token ni tekshiradi. JWT — bu foydalanuvchi login qilganda berilgan maxsus raqamli imzolangan ma'lumot. Har bir so'rovda shu token yuboriladi.

npm install @nestjs/jwt
// common/guards/jwt-auth.guard.ts
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
} from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(private readonly jwtService: JwtService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();

    // Authorization header dan token olamiz
    // Format: "Bearer eyJhbGciOiJIUzI1NiJ9..."
    const authHeader = request.headers["authorization"];

    if (!authHeader) {
      throw new UnauthorizedException("Token mavjud emas");
    }

    // "Bearer " qismini olib tashlaymiz
    const token = authHeader.split(" ")[1];

    if (!token) {
      throw new UnauthorizedException("Token noto'g'ri formatda");
    }

    try {
      // Token ni tekshiramiz va ichidagi ma'lumotni olamiz
      const payload = this.jwtService.verify(token);

      // Foydalanuvchi ma'lumotini so'rovga qo'shamiz
      // Controller da @Req() orqali olish mumkin bo'ladi
      request.user = payload;

      return true;
    } catch (error) {
      throw new UnauthorizedException("Token yaroqsiz yoki muddati o'tgan");
    }
  }
}

jwtService.verify(token) — token ni tekshiradi. Token to'g'ri bo'lsa — ichidagi ma'lumotni qaytaradi. Noto'g'ri bo'lsa — xato otadi.

request.user = payload — bu juda muhim qator. Foydalanuvchi ma'lumotini so'rovga yozib qo'yamiz. Keyingi bosqichlarda — Controller, Service — bu ma'lumotni olish mumkin bo'ladi.

Controller da foydalanuvchini olish:

import { Controller, Get, UseGuards, Req } from "@nestjs/common";
import { JwtAuthGuard } from "../common/guards/jwt-auth.guard";

@Controller("profile")
export class ProfileController {
  @Get()
  @UseGuards(JwtAuthGuard)
  getProfile(@Req() req: any) {
    // Guard request.user ga yozgan edi — shu yerda olamiz
    return req.user;
  }
}

Role-based Authorization — rol asosida ruxsat

Ba'zan faqat login qilgan bo'lish yetarli emas. Masalan, foydalanuvchini o'chirish — faqat admin rol egasi qila olishi kerak. Bu — rol asosida ruxsat berish.

Buning uchun avval @Roles() decorator yaratamiz:

// common/decorators/roles.decorator.ts
import { SetMetadata } from "@nestjs/common";

export enum Role {
  USER = "user",
  MODERATOR = "moderator",
  ADMIN = "admin",
}

// Bu decorator endpointga "kerakli rol" ma'lumotini yozadi
export const Roles = (...roles: Role[]) => SetMetadata("roles", roles);

SetMetadata — bu NestJS ning qurilmasi. U berilgan ma'lumotni route ga "yorliq" sifatida yopishtirib qo'yadi. Guard keyinchalik bu yorliqni o'qiy oladi.

Endi RolesGuard yozamiz:

// common/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { Role } from "../decorators/roles.decorator";

@Injectable()
export class RolesGuard implements CanActivate {
  // Reflector — metadata ni o'qish uchun NestJS qurilmasi
  constructor(private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    // Bu endpointga qaysi rollar kerakligini o'qiymiz
    const requiredRoles = this.reflector.getAllAndOverride<Role[]>("roles", [
      context.getHandler(), // metod darajasidagi metadata
      context.getClass(), // controller darajasidagi metadata
    ]);

    // Agar rol ko'rsatilmagan bo'lsa — hamma o'ta oladi
    if (!requiredRoles) {
      return true;
    }

    // So'rovdan foydalanuvchini olamiz
    // JwtAuthGuard avval ishlagan va request.user ga yozgan bo'lishi kerak
    const { user } = context.switchToHttp().getRequest();

    // Foydalanuvchining roli kerakli rollar ichida bormi?
    return requiredRoles.some((role) => user?.role === role);
  }
}

Reflector — bu NestJS ning maxsus xizmati. U SetMetadata bilan yozilgan ma'lumotlarni o'qiydi.

getAllAndOverride — avval metod darajasidagi metadata ni qidiradi, topilmasa controller darajasidagini oladi. Bu juda qulay — controller da umumiy rol, metodda aniq rol belgilash imkonini beradi.


Guard larni birga ishlatish

JwtAuthGuard va RolesGuard birga ishlashi kerak. Avval token tekshiriladi, keyin rol:

// users/users.controller.ts
import { Controller, Get, Delete, Param, UseGuards } from "@nestjs/common";
import { JwtAuthGuard } from "../common/guards/jwt-auth.guard";
import { RolesGuard } from "../common/guards/roles.guard";
import { Roles, Role } from "../common/decorators/roles.decorator";

@Controller("users")
@UseGuards(JwtAuthGuard, RolesGuard) // ketma-ketlik muhim — avval JWT, keyin Rol
export class UsersController {
  @Get()
  @Roles(Role.USER, Role.ADMIN) // user va admin ko'ra oladi
  findAll() {
    return "Foydalanuvchilar ro'yxati";
  }

  @Delete(":id")
  @Roles(Role.ADMIN) // faqat admin o'chira oladi
  remove(@Param("id") id: number) {
    return `${id}-foydalanuvchi o\'chirildi`;
  }
}

Guard lar ham Pipe lar kabi chapdan o'ngga ketma-ket ishlaydi. JwtAuthGuard avval ishlaydi — token tekshiriladi, request.user ga yoziladi. Keyin RolesGuard ishlaydi — request.user.role ni o'qiydi va tekshiradi.

Agar JwtAuthGuard false qaytarsa — RolesGuard umuman ishlamaydi. Bu to'g'ri xatti-harakat — avval kim ekanligini bilib ol, keyin nimaga ruxsati borligini tekshir.


@Public() decorator — ochiq endpointlar

Global Guard qo'yilganda login va ro'yxatdan o'tish endpointlari ham himoyalanib qoladi. Bu muammo. Yechim — @Public() decorator:

// common/decorators/public.decorator.ts
import { SetMetadata } from "@nestjs/common";

export const IS_PUBLIC_KEY = "isPublic";
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

Endi JwtAuthGuard ni yangilaymiz — @Public() bo'lsa o'tkazib yuboradi:

// common/guards/jwt-auth.guard.ts
import { IS_PUBLIC_KEY } from "../decorators/public.decorator";

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(
    private readonly jwtService: JwtService,
    private readonly reflector: Reflector,
  ) {}

  canActivate(context: ExecutionContext): boolean {
    // Bu endpoint @Public() bilan belgilanganmi?
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    // Ha bo'lsa — tekshirmasdan o'tkazib yuboramiz
    if (isPublic) {
      return true;
    }

    // Qolgan logika avvalgidek...
    const request = context.switchToHttp().getRequest();
    const authHeader = request.headers["authorization"];

    if (!authHeader) {
      throw new UnauthorizedException("Token mavjud emas");
    }

    const token = authHeader.split(" ")[1];

    try {
      const payload = this.jwtService.verify(token);
      request.user = payload;
      return true;
    } catch {
      throw new UnauthorizedException("Token yaroqsiz yoki muddati o'tgan");
    }
  }
}

Auth controller da ishlatish:

// auth/auth.controller.ts
import { Controller, Post, Body } from "@nestjs/common";
import { Public } from "../common/decorators/public.decorator";

@Controller("auth")
export class AuthController {
  @Post("login")
  @Public() // bu endpoint ochiq — token shart emas
  login(@Body() body: any) {
    return "Login muvaffaqiyatli";
  }

  @Post("register")
  @Public() // bu ham ochiq
  register(@Body() body: any) {
    return "Ro'yxatdan o'tish muvaffaqiyatli";
  }
}

Xulosa

Guard — NestJS da ruxsat boshqaruvining asosi. Har bir so'rov Guard dan o'tadi va faqat ruxsati bor foydalanuvchilar kerakli endpointlarga kira oladi.

Eslab qoling:

  • CanActivate — Guard ning interfeysi, canActivate metodi true/false qaytaradi
  • ExecutionContext — so'rov haqida hamma ma'lumot shu yerda
  • @UseGuards() — Guard ni ulash
  • SetMetadata — endpointga ma'lumot yopishtirib qo'yish
  • Reflector — o'sha ma'lumotni Guard ichida o'qish
  • Guard lar ketma-ket ishlaydi — avvalgisi false qaytarsa keyingisi ishlamaydi
  • @Public() — global Guard dan istisno qilish uchun

Keyingi dars

10-dars: Interceptors — Response Mapping, Logging