IlmHamroh
JavaScript Full-stack/30-qism. NestJS55/65-dars21 daqiqa
Mundarija (33)

Passport NestJS'da: strategiya, validate() va AuthGuard

Qisqacha: Passport — Node.js'da kimlikni tekshirishning eng ko'p ishlatiladigan kutubxonasi. Har usul (email + parol, JWT, Google) — alohida strategiya. Nest'da strategiya PassportStrategy(Strategy) dan meros oladi. Uning validate() metodi foydalanuvchini qaytaradi va natija req.user ga yoziladi. Marshrutni esa @UseGuards(AuthGuard("jwt")) himoyalaydi. Kanon o'lchovida Passport 28-qismdagi o'z kodimiz bilan bir xil tezlikda ishladi. Lekin 401 javobi Problem Details emas edi va 3 ta qo'shimcha paket kerak bo'ldi — shuning uchun vazifalar-api da Passport yo'q.

Bu darsda

  • Passport strategiyasi nima ekanini va Nest uni qanday guard'ga aylantirishini tushuntirasiz.
  • LocalStrategy (email + parol) va JwtStrategy (Bearer token) yozasiz, validate() nima qaytarishi kerakligini bilasiz.
  • @nestjs/jwt bilan token chiqarasiz va soxta token nega 401 olishini haqiqiy so'rovlarda ko'rasiz.
  • Kanon o'lchovi bilan Passport'ni o'z guard'imiz bilan solishtirasiz.
  • vazifalar-api ga 28-qismning auth fayllarini qaytarib, ko'chishning birinchi qadamini qo'yasiz.

Oldin bilishingiz kerak: Guard'lar, Session-based auth, JWT anatomiyasi, Access va refresh token strategiyasi, Custom dekoratorlar.

Auth'ni ataylab oxirga qoldirdik. Sababi oddiy: endi butun loyiha — controller'lar, xizmatlar, Swagger, health, SSE, testlar — Nest'da ishlayapti. 28-qismda Express uchun yozgan autentifikatsiyamiz shu tayyor uyga ko'chadi. Uy tayyor bo'lgani uchun har qadamda faqat bitta narsa o'zgaradi: kim so'rayotganini aniqlash Nest'da qanday bo'ladi.

1. Nega bu kerak?

28-qismda "kim so'rayapti" savoliga ikki funksiya javob berardi. kimlik() cookie'dagi sessiyani tekshirib, req.sessiya ni qo'yardi. kirishKerak esa sessiya bo'lmasa 401 qaytarardi. Kod kichik va bizniki edi.

Endi istalgan Nest vakansiyasini oching. Talablarda deyarli doim bir qator bor: "Passport, JWT". Nest hujjatining autentifikatsiya bo'limi ham Passport bilan boshlanadi. Ya'ni boshqa jamoaning Nest loyihasiga kirsangiz, katta ehtimol bilan AuthGuard("jwt") ni ko'rasiz.

Shuning uchun bu darsda ikki ish qilamiz. Avval Passport'ni Nest'da to'liq o'rganamiz: o'z demo loyihamizda haqiqiy so'rovlar bilan. Keyin kanon o'lchovi bilan ko'ramiz: nega vazifalar-api da uni ishlatmadik. Bu "Passport yomon" degani emas. Bu — aniq loyiha uchun o'lchab qilingan tanlov.

O'xshatish: Passport — universal kalitlar to'plami. Har eshik (Google, parol, JWT) uchun tayyor kalit bor. Agar uyingizda bitta eshik bo'lsa va o'z kalitingiz allaqachon ishlab tursa, to'plam og'irlik qiladi. Ko'p eshikli binoda esa to'plam vaqtni tejaydi.

2. Passport qanday ishlaydi

2.1 Strategiya — kimlikni aniqlash usuli

Strategiya (strategy) — bitta savolga javob beradigan kichik obyekt: "shu so'rovda kim bor?". passport-local email va parolni tanadan o'qiydi. passport-jwt sarlavhadagi tokenni tekshiradi. passport-google-oauth20 Google'ga yo'naltiradi. Passport'ning o'zi esa faqat strategiyani chaqiradi va natijaga qarab "o'tkaz" yoki "401" deydi.

Passport ekotizimida yuzlab tayyor strategiya bor. Kutubxonaning asosiy paketi passport 0.7.0 — 2023-yil noyabrdan beri yangi versiya chiqmagan. Nest uchun o'rami — @nestjs/passport 12.0.0 (2026-08-27, Nest 12 bilan birga chiqqan).

2.2 Nest'da uch qism

Nest'da Passport uch bo'lakdan yig'iladi:

Bo'lak Vazifasi
PassportStrategy(Strategy, "nom") strategiyani Nest provider'iga aylantiradi
validate(...) sizning kodingiz: foydalanuvchini toping yoki rad eting
AuthGuard("nom") marshrutga qo'yiladigan guard

Bog'lanish nom orqali: strategiya "jwt" nomi bilan ro'yxatdan o'tadi, guard AuthGuard("jwt") shu nomni chaqiradi. Nom berilmasa — strategiyaning o'z nomi olinadi (passport-local uchun "local", passport-jwt uchun "jwt").

2.3 Paketsiz model

G'oyani kichik JavaScript modelida ko'ramiz. Bu blok brauzerda ham ishlaydi. Haqiqiy Passport murakkabroq (sessiya, yo'naltirish, bir nechta strategiya), lekin yadrosi shu:

js
class Passport {
  #strategiyalar = new Map();
  use(nom, strategiya) {
    this.#strategiyalar.set(nom, strategiya);
  }
  async authenticate(nom, req) {
    const s = this.#strategiyalar.get(nom);
    if (!s) {
      throw new Error(`Unknown authentication strategy "${nom}"`);
    }
    const user = await s.validate(req);
    if (!user) return 401;
    req.user = user; // validate() natijasi
    return 200;
  }
}

const passport = new Passport();
passport.use("local", {
  validate(req) {
    const { email, parol } = req.body ?? {};
    if (email === "aziz@example.com" && parol === "Bahor-2026") {
      return { id: 1, email };
    }
    return null;
  },
});

const a = { body: { email: "aziz@example.com", parol: "xato" } };
const b = {
  body: { email: "aziz@example.com", parol: "Bahor-2026" },
};
console.log(await passport.authenticate("local", a), a.user);
console.log(await passport.authenticate("local", b), b.user);
try {
  await passport.authenticate("jwt", {});
} catch (e) {
  console.log(e.message);
}

Konsolda:

text
401 undefined
200 { id: 1, email: 'aziz@example.com' }
Unknown authentication strategy "jwt"

Uch qatorga qarang. validate() null qaytardi — 401, req.user yo'q. Obyekt qaytardi — 200 va obyekt req.user ga yozildi. Ro'yxatda yo'q nom — xato. Oxirgi xabar haqiqiy Passport'niki bilan aynan bir xil, uni "Ko'p uchraydigan xatolar" bo'limida ko'ramiz. Modelda soddalashtirgan joyimiz: parol ochiq solishtirilyapti — haqiqiy kodda hash va timingSafeEqual (parolni xavfsiz saqlash).

Tekshirib ko'ring: validate() foydalanuvchi o'rniga true qaytarsa nima bo'ladi?

Javob

So'rov o'tadi (200), lekin req.user ga true yoziladi. Keyingi kod req.user.id ni o'qisa — undefined. Passport faqat "falsy yoki yo'q" ni tekshiradi. Shuning uchun validate() doim kerakli maydonlari bor obyekt qaytarishi kerak.

2.4 Passport sessiyasi va Nest

Passport Express dunyosida tug'ilgan. U yerda u odatda express-session bilan birga ishlaydi: kirgandan keyin foydalanuvchini sessiyaga yozadi (serializeUser), keyingi so'rovda o'qiydi (deserializeUser). Eski Express darsliklarida shu ikki funksiyani ko'p uchratasiz.

Nest'da bu sukut bo'yicha yo'q. @nestjs/passport strategiyani sessiyasiz chaqiradi: har so'rov o'z tokenini yoki parolini olib keladi, Passport uni tekshiradi va unutadi. Bu JWT uchun to'g'ri, chunki token o'zi "sessiya".

Sukutni modul darajasida o'zgartirish mumkin: PassportModule.register({ defaultStrategy: "jwt" }) — shunda AuthGuard() nom bermasdan ham "jwt" ni chaqiradi. session: true ham bor, lekin u express-session ni talab qiladi. 28-qismdagi server sessiyamiz esa o'zimizniki: sessiyalar jadvali, hash'langan token, __Host-sessiya cookie. Uni Passport'ning sessiya mexanizmiga bog'lash shart emas — buni kanon tajribasida ko'ramiz.

3. LocalStrategy — email va parol

3.1 Strategiya

O'z demo loyihamizda (nest new, NestJS 12.2.0) to'rt paket o'rnatdik: @nestjs/passport@12.0.0, passport@0.7.0, passport-local@1.0.0, @types/passport-local@1.0.38. Foydalanuvchilar — xotiradagi kichik ro'yxat (FoydalanuvchilarService.tekshir(email, parol) — scrypt hash bilan).

ts
// local.strategy.ts — email + parol bilan kirish (passport-local)
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-local";
import { FoydalanuvchilarService } from "../foydalanuvchilar.js";
import type { Foydalanuvchi } from "../foydalanuvchilar.js";

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly foyd: FoydalanuvchilarService) {
    // sukut: "username" va "password" maydonlari — bizda boshqacha
    super({ usernameField: "email", passwordField: "parol" });
  }

  // Passport tanadan email va parolni olib, shu metodni chaqiradi.
  // Qaytgan qiymat — req.user ga yoziladi
  validate(email: string, parol: string): Foydalanuvchi {
    const f = this.foyd.tekshir(email, parol);
    if (f === null) throw new UnauthorizedException();
    return f;
  }
}

Qatorma-qator:

  • PassportStrategy(Strategy) — bu funksiya mixin: u passport-local ning Strategy klassini olib, Nest provider'i bo'la oladigan yangi klass qaytaradi. Biz undan meros olamiz.
  • super({...}) — strategiya sozlamalari. passport-local sukut bo'yicha username va password maydonlarini qidiradi. Biz email va parol deymiz.
  • validate(email, parol) — argumentlar strategiyaga bog'liq. Local'da — ikki satr, JWT'da — token ichidagi obyekt.
  • UnauthorizedException — Nest'ning tayyor 401 xatosi.

Demodagi importlar .js bilan yozilgan — nest new shabloni va Nest hujjati shunday. vazifalar-api da esa .ts (Nest CLI va loyiha tuzilmasi).

3.2 Controller va modul

ts
@Controller("auth")
export class AuthController {
  constructor(private readonly jwt: JwtService) {}

  @Post("kirish")
  @HttpCode(200)
  @UseGuards(AuthGuard("local"))
  async kirish(@Req() req: Request): Promise<{ access: string }> {
    const f = req.user as Foydalanuvchi; // LocalStrategy.validate()
    const access = await this.jwt.signAsync(
      { sub: f.id, rol: f.rol });
    return { access };
  }

  @Get("men")
  @UseGuards(AuthGuard("jwt"))
  men(@Req() req: Request): object {
    return { user: req.user }; // JwtStrategy.validate()
  }
}

kirish metodi ichida parol tekshiruvi yo'q. Uni guard qildi. Metodga kelganda req.user allaqachon tayyor. @HttpCode(200) — POST sukut bo'yicha 201 qaytaradi (Controller'lar), kirish esa hech narsa "yaratmaydi".

req.user as Foydalanuvchi — Passport req.user ga tur bermaydi (Express.User — bo'sh interfeys). Kattaroq loyihada @Foydalanuvchi() kabi param dekorator yoziladi (Custom dekoratorlar).

Modul hammasini ulaydi:

ts
@Module({
  imports: [
    PassportModule,
    JwtModule.register({
      secret: JWT_SIRI,
      signOptions: { expiresIn: "15m", algorithm: "HS256" },
    }),
  ],
  controllers: [AuthController],
  providers: [FoydalanuvchilarService, LocalStrategy, JwtStrategy],
})
export class AuthModule {}

E'tibor bering: strategiyalar providers da. Nest ularni yaratganda constructor ichidagi super() strategiyani Passport'ga ro'yxatdan o'tkazadi. Provider bo'lmasa — strategiya ham yo'q.

4. JwtStrategy va @nestjs/jwt

4.1 Token chiqarish

@nestjs/jwt 12.0.3 — jsonwebtoken kutubxonasining Nest o'rami. JwtModule.register() sirni va sukut sozlamalarni oladi, JwtService esa signAsync() va verifyAsync() beradi. 15 daqiqalik access token — access va refresh strategiyasi darsidagi muddat.

Demoda sir koddagi satr. Haqiqiy loyihada u ConfigService dan keladi va kamida 32 tasodifiy bayt bo'ladi (@nestjs/config). Buning uchun register() emas, registerAsync() ishlatiladi — u dinamik modul darsidagi useFactory + inject naqshining o'zi:

ts
JwtModule.registerAsync({
  inject: [ConfigService],
  useFactory: (c: ConfigService) => ({
    secret: c.getOrThrow<string>("JWT_SIRI"),
    signOptions: { expiresIn: "15m", algorithm: "HS256" },
  }),
}),

getOrThrow — sir berilmagan bo'lsa ilova ishga tushmaydi. Bu "sukut bo'yicha xavfsiz" tamoyili (xavfsizlik tamoyillari): sirsiz ishlab ketgan server hamma tokenni bo'sh satr bilan imzolashi mumkin edi.

4.2 Tokenni tekshirish

ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, "jwt") {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false, // exp o'tgan — 401
      secretOrKey: JWT_SIRI,
      algorithms: ["HS256"], // faqat shu algoritm (alg:none yo'q)
    });
  }

  // imzo va exp tekshirilgach chaqiriladi: payload — ishonchli
  validate(p: Tolov): { id: number; rol: Rol } {
    return { id: p.sub, rol: p.rol };
  }
}

Bu yerda validate() ga token emas, uning ochilgan va tekshirilgan payload'i keladi. Imzo noto'g'ri bo'lsa yoki exp o'tgan bo'lsa, validate() umuman chaqirilmaydi — passport-jwt o'zi 401 beradi.

ExtractJwt — tokenni qayerdan olishni aytadi: sarlavhadan, cookie'dan, query'dan. Keyingi darsda cookie variantini yozamiz.

algorithms: ["HS256"] ni unutmang. JWT anatomiyasi darsida alg: none va algoritm almashtirish hujumlarini ko'rgansiz. Ruxsat ro'yxati (allowlist) bo'lsa, kutubxona boshqa algoritmli tokenni ochmaydi.

4.3 Biz ishga tushirdik

Demo serverni listen(0) bilan ochib, fetch bilan yetti so'rov yubordik (Node 24.21.0, NestJS 12.2.0, @nestjs/passport 12.0.0, passport-jwt 4.0.1). Token har safar boshqacha (iat vaqtga bog'liq) — uni qisqartirdik:

text
1) noto'g'ri parol → 401 {"message":"Unauthorized","statusCode":401}
2) tana bo'sh → 401 {"message":"Unauthorized","statusCode":401}
3) to'g'ri parol → 200 {"access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ…"}
   sarlavha: {"alg":"HS256","typ":"JWT"}
   payload: {"sub":1,"rol":"foydalanuvchi","iat":…,"exp":… (+900 s)}
4) tokensiz → 401 {"message":"Unauthorized","statusCode":401}
5) token bilan → 200 {"user":{"id":1,"rol":"foydalanuvchi"}}
6) rol admin'ga o'zgartirilgan → 401 {"message":"Unauthorized","statusCode":401}
7) alg: none → 401 {"message":"Unauthorized","statusCode":401}

Chiqishni o'qiymiz:

  • 1 va 2 — LocalStrategy. Tana bo'sh bo'lsa passport-local validate() ni chaqirmaydi ham — o'zi rad etadi.
  • 3 — payload'da exp − iat = 900 soniya, ya'ni 15 daqiqa. Token ichida parol yo'q, faqat sub va rol.
  • 5 — req.user endi JwtStrategy.validate() qaytargan obyekt.
  • 6 — payload'dagi rol ni admin ga almashtirib, eski imzoni qoldirdik. Imzo mos kelmadi — 401.
  • 7 — alg: none, imzosiz token. algorithms ro'yxatida yo'q — 401.

To'rtta 401 javobning tanasi bir xil: {"message":"Unauthorized"}. Bu xavfsizlik uchun yaxshi (sababini oshkor qilmaydi). Lekin bu Problem Details formati emas va WWW-Authenticate sarlavhasi ham yo'q. Bu farqqa hali qaytamiz.

sequenceDiagram
  participant M as Mijoz
  participant G as AuthGuard("jwt")
  participant P as Passport
  participant S as JwtStrategy
  participant C as Controller
  M->>G: GET /auth/men (Bearer)
  G->>P: authenticate("jwt")
  P->>S: imzo va exp tekshiruvi
  alt token yaroqli
    S->>P: validate(payload)
    P->>G: req.user
    G->>C: men(req)
  else yaroqsiz
    P->>G: xato
    G->>M: 401
  end

Diagrammada guard o'zi hech narsa tekshirmayapti. U faqat Passport'ni chaqiradi, Passport — strategiyani. Nest'ning so'rov hayot sikli bo'yicha guard pipe va handler'dan oldin turadi, shuning uchun noto'g'ri token controller'gacha yetmaydi.

Tekshirib ko'ring: 6-so'rovda hujumchi faqat payload'ni o'zgartirdi. Nega server buni sezdi, axir payload shifrlanmagan?

Javob

Imzo sarlavha + payload ustidan hisoblanadi. Payload bitta harfga o'zgarsa ham, HMAC boshqacha chiqadi va eski imzo mos kelmaydi. JWT ma'lumotni yashirmaydi (base64url — shifr emas), lekin o'zgartirilganini aniqlaydi. Shuning uchun payload'ga sir yozilmaydi, lekin unga ishonsa bo'ladi — imzo tekshirilgandan keyin.

5. Kanon o'lchovi: Passport vs o'z guard'imiz

5.1 Tajriba

Kanon 28-qismdagi sessiyani Passport strategiyasi sifatida ham yozdi (passport-custom 1.2.1 — o'z validate(req) funksiyangiz bilan strategiya). Keyin uch varianti bir xil marshrutda o'lchadi: kanondagi kimlik middleware + KirishGuard, Passport strategiyasi va JWT (bazasiz tekshiruv). 32 parallel ulanish, 3 soniya, ikki aylanish.

Kimlikni tekshirish: kanonga nisbatan so'rov/soniya
  • Kanon (sessiya)100 %
  • Passport (sessiya)102 %
  • JWT HS256153 %

Manba: IlmHamroh kanon o'lchovi, 2026-10-31: Node 24.21.0, NestJS 12.2.0, @nestjs/passport 12.0.0, @nestjs/jwt 12.0.3, Postgres 18; ikki aylanish o'rtachasi (2330 / 2383 / 3567 req/s); bitta mashina, bir vaqtda, mashina band

Grafikdan ikki xulosa:

  • Passport bilan o'z guard'imiz orasida farq yo'q: 2 % — o'lchov shovqini ichida. Ikkalasi ham har so'rovda Postgres'dan sessiyani o'qiydi. Vaqtni baza oladi, kutubxona emas.
  • JWT ~1.5 baravar ko'p so'rov o'tkazdi, chunki bazaga bormaydi. Bu narxi bor tezlik — keyingi darsda o'lchaymiz.

5.2 401 javobi

Kanon chiqishidan, cookie'siz so'rov (tana qisqartirilgan):

text
kanon     401 application/problem+json; charset=utf-8 www-authenticate=Cookie realm="vazifalar" tana={"type":"https://vazifalar-api.example/xatolar/kirish-kerak","title":"Kirish kerak","status":401,"detail":"Avval tizimga
passport  401 application/json; charset=utf-8 www-authenticate=— tana={"message":"Unauthorized","statusCode":401}

25-qismdan beri vazifalar-api ning hamma xatosi Problem Details (RFC 9457 darsi). Frontend detail ni ko'rsatadi, kod bo'yicha qaror qiladi. Passport'ning 401 tanasi bu shartnomani buzadi. Tuzatsa bo'ladi: AuthGuard ning handleRequest() metodini qayta yozib, o'z xatoingizni tashlaysiz. Lekin bu — yana kod.

5.3 Nega kanonda yo'q

Mezon Passport Kanon (kimlik + KirishGuard)
Tezlik teng (~2.4k req/s) teng (~2.3k req/s)
Paketlar +3 0
401 tanasi {"message":…} Problem Details
WWW-Authenticate yo'q bor (RFC 9110)

Passport'ning asl kuchi — ko'p tashqi provayder uchun tayyor strategiyalar. Bizda bitta tashqi provayder bor (Google, OIDC), u ham 28-qismda openid-client bilan to'g'ridan yozilgan. Ya'ni Passport bizga yangi imkoniyat bermaydi, faqat qatlam qo'shadi. Kanon qarori (docs/AUTH-QAROR.md): Passport ishlatilmaydi; o'nlab tashqi provayder kerak bo'lsa — qayta ko'riladi.

Tekshirib ko'ring: Passport va kanon guard'i bir xil tezlikda ekan. Unda nega kanon "+3 paket" ni jiddiy kamchilik deb hisobladi?

Javob

Har paket — kuzatish kerak bo'lgan kod: yangilanishlar, zaifliklar, tranzitiv bog'liqliklar (ta'minot zanjiri xavfsizligi). Foyda bo'lmasa, narx qoladi. Passport bu loyihada yangi imkoniyat bermaydi (strategiyalar kerak emas), lekin 401 formatini tuzatish uchun qo'shimcha kod talab qiladi. Tezlik teng bo'lganda hal qiluvchi narsa — soddalik va javob shartnomasi.

Maslahat: Bu qaror sizning keyingi ish joyingizga taalluqli emas. Jamoada Passport bo'lsa — yuqoridagi uch bo'lakni bilganingiz yetarli. Yangi loyiha boshlasangiz — o'lchang va tanlang.

6. Hujumchi nigohi

Bu darsdagi kodni hujumchi qayerdan buzishga urinadi:

  1. Parol tanlash. AuthGuard("local") o'zi tezlikni cheklamaydi. Himoyasiz POST /auth/kirish — credential stuffing uchun ochiq eshik. Himoya — tezlik cheklovi (@nestjs/throttler va kanon CheklovGuard).
  2. Foydalanuvchini sanash. "Bunday email yo'q" va "parol noto'g'ri" — ikki xil xabar bo'lsa, hujumchi qaysi email ro'yxatda borligini biladi. Demodagi validate() ikkalasida bir xil UnauthorizedException tashlaydi.
  3. Algoritmni almashtirish. algorithms berilmasa, ba'zi kutubxona versiyalari tokendagi alg ga ishonadi. Biz 7-so'rovda ko'rdik: ro'yxat bor — alg: none o'tmadi.
  4. Kuchsiz sir. HS256 siri qisqa bo'lsa, tokenni offline tanlab topish mumkin (JWT anatomiyasi). Sir — kamida 32 tasodifiy bayt, kodda emas, .env da.
  5. validate() da bazaga qaramaslik. JwtStrategy faqat payload'ni qaytaradi. Foydalanuvchi bloklangan bo'lsa ham token 15 daqiqa ishlaydi. Bu masalani keyingi darsda o'lchaymiz.

7. Ko'p uchraydigan xatolar

Strategiya providers ga qo'shilmagan, lekin AuthGuard("jwt") ishlatilgan. Demoda shu holatni yasadik:

text
ERROR [ExceptionsHandler] Error: Unknown authentication strategy "jwt"
    at attempt (…\node_modules\passport\lib\middleware\authenticate.js:193:39)
GET /auth/men → 500 {"statusCode":500,"message":"Internal server error"}

Tarjimasi: "Noma'lum autentifikatsiya strategiyasi: jwt". Bu 401 emas, 500 — dasturchi xatosi. Ilova ishga tushadi va xato faqat birinchi so'rovda chiqadi. Davosi — strategiyani modulning providers iga qo'shing va nomni tekshiring ("jwt" va "JWT" — ikki xil nom). Iz qatorini qisqartirdik.

Belgi Sabab Davosi
To'g'ri parolga ham 401 usernameField berilmagan, tanada email super({ usernameField: "email" })
Kirish 201 qaytaradi POST sukuti @HttpCode(200)
req.user.id — undefined validate() true qaytargan foydalanuvchi obyektini qaytaring
Muddati o'tgan token o'tadi ignoreExpiration: true false (sukut)
500 Unknown authentication strategy strategiya provider emas providers: [JwtStrategy]

8. Mashqlar

Mashqlar kurs/mashqlar/30/55-passport/ da.

1-mashq (oson): bo'shliqlarni to'ldiring

  • Strategiyani Nest provider'iga aylantiradigan funksiya — (Strategy)
  • validate() qaytargan qiymat yoziladigan joy — req.
  • Marshrutni JWT bilan himoyalash — @UseGuards(AuthGuard(""))
  • passport-local sukut bo'yicha qidiradigan parol maydoni —
  • Kanon o'lchovida Passport kanon guard'iga nisbatan necha foiz tezlik ko'rsatdi? —
Yechim
  • PassportStrategy — mixin: strategiya klassini olib, Nest provider'i bo'la oladigan klass qaytaradi.
  • user — req.user; Passport uni validate() natijasidan qo'yadi.
  • jwt — passport-jwt strategiyasining sukut nomi.
  • password — shuning uchun bizda passwordField: "parol".
  • 102 — farq o'lchov shovqini ichida: vaqtni baza oladi.

2-mashq (o'rta): bir nechta strategiya

Haqiqiy AuthGuard massiv qabul qiladi: AuthGuard(["jwt", "apikalit"]) — birinchi muvaffaqiyatli strategiya yutadi. Darsdagi modelga authenticateBiri(nomlar, req) metodini qo'shing: nomlarni tartib bilan sinasin, birinchi 200 da to'xtasin, hammasi 401 bo'lsa — 401. Ishora: oddiy for...of va mavjud authenticate().

Yechim
js
class Passport {
  #strategiyalar = new Map();
  use(nom, s) {
    this.#strategiyalar.set(nom, s);
  }
  async authenticate(nom, req) {
    const s = this.#strategiyalar.get(nom);
    if (!s) {
      throw new Error(`Unknown authentication strategy "${nom}"`);
    }
    const user = await s.validate(req);
    if (!user) return 401;
    req.user = user;
    return 200;
  }
  async authenticateBiri(nomlar, req) {
    for (const nom of nomlar) {
      if ((await this.authenticate(nom, req)) === 200) return 200;
    }
    return 401;
  }
}

const p = new Passport();
p.use("jwt", {
  validate: (req) => (req.token === "t-1" ? { id: 1 } : null),
});
p.use("apikalit", {
  validate: (req) =>
    (req.kalit === "vz_1" ? { id: 1, kalit: true } : null),
});
const a = { kalit: "vz_1" };
console.log(await p.authenticateBiri(["jwt", "apikalit"], a), a.user);
console.log(await p.authenticateBiri(["jwt", "apikalit"], {}));

Konsolda:

text
200 { id: 1, kalit: true }
401

Birinchi so'rovda jwt 401 berdi, lekin to'xtamadik — apikalit o'tdi. Kanondagi kimlik() ham shunday ikki yo'lni biladi (Bearer API kaliti yoki cookie sessiyasi), faqat strategiyalarsiz, bitta funksiyada.

3-mashq (qiyin): Problem Details 401

Passport'ning 401 tanasi {"message":"Unauthorized"}. Nest'da buni AuthGuard ning handleRequest(err, user) metodini qayta yozib tuzatishadi: user yo'q bo'lsa — o'z xatoingizni tashlaysiz. Modelda handleRequest(err, user, yol) funksiyasini yozing: err bo'lsa — uni qaytarsin, user bo'lmasa — { status: 401, sarlavhalar, tana } obyektini qaytarsin, bunda WWW-Authenticate: Bearer realm="vazifalar" va Problem Details tanasi (type, title, status, instance) bo'lsin; user bo'lsa — user ning o'zini. Ishora: obyekt va JSON.

Yechim
js
const TUR = "https://vazifalar-api.example/xatolar/kirish-kerak";

function handleRequest(err, user, yol) {
  if (err) return err;
  if (!user) {
    return {
      status: 401,
      sarlavhalar: {
        "Content-Type": "application/problem+json",
        "WWW-Authenticate": 'Bearer realm="vazifalar"',
      },
      tana: JSON.stringify({
        type: TUR, title: "Kirish kerak", status: 401, instance: yol,
      }),
    };
  }
  return user;
}

console.log(handleRequest(null, { id: 1 }, "/v1/auth/men"));
const j = handleRequest(null, false, "/v1/auth/men");
console.log(j.status, j.sarlavhalar["WWW-Authenticate"]);
console.log(j.tana);

Konsolda:

text
{ id: 1 }
401 Bearer realm="vazifalar"
{"type":"https://vazifalar-api.example/xatolar/kirish-kerak","title":"Kirish kerak","status":401,"instance":"/v1/auth/men"}

Nest'da xuddi shu mantiq class JwtGuard extends AuthGuard("jwt") ichida yoziladi va obyekt o'rniga xato tashlanadi — uni Problem Details filtri javobga aylantiradi (Exception filter'lar). Ishlaydi, lekin endi Passport ustiga o'z kodimiz qo'shildi — kanon nega to'g'ridan o'z guard'ini tanlaganining yana bir sababi.

9. Loyiha qadami: 28-qism auth fayllarini qaytarish

9.1 Reja

Ko'chish yetti darsga bo'linadi. Har darsda bitta qatlam qo'shiladi, commit esa oxirida — Autentifikatsiya II darsida:

flowchart LR
  A["Kim: kimlik, egasi"] --> B["Sessiya cookie"]
  B --> C["Global guard"]
  C --> D["Egalik va RLS"]
  D --> E["Kirish yo'llari"]
  E --> F["Tezlik, CAPTCHA"]
  F --> G["Modul, testlar, commit"]

Bugun — birinchi quti: "kim so'rayapti". Bu qatlam guard'lardan oldin, oddiy Express middleware sifatida ishlaydi. Sababini global guard darsida ko'ramiz.

9.2 Paketlar va fayllar

vazifalar-api papkasida (C:/Users/…/vazifalar-api):

bash
npm i --save-exact @simplewebauthn/server@14.0.3 openid-client@6.8.8
npm i -D --save-exact @simplewebauthn/browser@14.0.0 \
  oidc-provider@9.12.2 @types/oidc-provider@9.12.1 playwright@1.64.0

package.json ga 28-qismdagi overrides bo'limini ham qaytaring (jose 6.2.12 va @peculiar/asn1-* 2.11.0 — tranzitiv paketlar). Kanon yangi bog'liqliklarning hammasini (tranzitivlari bilan 55 ta) 7 kunlik qoidaga tekshirdi: 7 kundan yangi paket — 0.

28-qism yakuniy commit'ingizdan o'zgarishsiz qaytadigan fayllar (Express'ga bog'liq emas — toza mantiq):

Papka Fayllar
src/auth/ sessiya, parol, token, otp, totp, mfa, oidc, telegram, api-kalit, captcha, csrf, ombor, sxema, pochta, imzo-cookie, taqiqlangan-parollar
src/umumiy/ cheklov, fon, semafor, sir, xavfsiz-url, kursor (HMAC imzoli), webhook (SSRF himoyasi)
sql/, prisma/ 109–122 va ularning 11 ta Prisma migratsiyasi, schema.prisma

Bu fayllar qayerdan keladi? Nest CLI va loyiha tuzilmasi darsida ko'chishni 27-qism commit'idan yangi nest tarmog'ida boshlagandik; main esa 29-qism holatida ishlab turgan Express API bo'lib qoldi. Demak 28-qism commit'i main tarixida bor — uni git log main --oneline bilan topasiz. Siz nest tarmog'ida turib, kerakli fayllarni o'sha commit'dan olasiz. Masalan, 28-qism commit'i abc1234 bo'lsa: git checkout abc1234 -- src/auth/sessiya.ts. Bu buyruq tarmoqni almashtirmaydi — faqat bitta faylni o'sha commit'dagi holatiga keltiradi. Express'ga bog'langan marshrut.ts, mfa-marshrut.ts, admin.ts qaytmaydi — ularning o'rniga keyingi darslarda controller'lar yoziladi.

xizmat.ts ham qaytadi, bitta import o'zgaradi: MfaBogliqliklari turi Express routeridan (mfa-marshrut.ts) yangi kichik mfa-turlar.ts fayliga ko'chdi (28-qismdagi tur va MfaXatosi aynan).

9.3 middleware.ts — Nest shakli

28-qismdagi faylning ikki funksiyasidan biri qoladi, biri o'zgaradi. kimlik() — aynan. kirishKerak (401/403) — olib tashlandi: bu endi global guard'ning ishi. O'rniga egasiniAniqla() keldi — u faqat "kim" ni yozadi va hech kimni rad etmaydi. Kanon faylida qatorlar uzun edi — telefon uchun bo'ldik (izohlar qisqartirildi, kod ma'nosi aynan):

ts
// middleware.ts — kim so'rayapti. kimlik() — cookie'dagi sessiyani
// tekshiradi va req.sessiya ni qo'yadi (bo'lmasa — jim o'tadi);
// 30-qism: kirishKerak (401/403) — KirishGuard'ga ko'chdi
// (qoriqchi.ts); bu yerda egasiniAniqla() — req.egasi + CLS.
import type { RequestHandler } from "express";
import { IlovaXatosi } from "../umumiy/xatolar.ts";
import { cookieNomi, cookieOl } from "./sessiya.ts";
import type { SessiyaOmbori } from "./sessiya.ts";
import type { ApiKalitlar } from "./api-kalit.ts";

export class KirilmaganXatosi extends IlovaXatosi {
  constructor() {
    // RFC 9110: 401 javobida WWW-Authenticate bo'lishi kerak. Cookie
    // sessiyasi uchun standart sxema yo'q — o'zimizniki
    super(401, "Avval tizimga kiring", {
      kod: "kirish-kerak",
      sarlavha: "Kirish kerak",
      httpSarlavhalar: {
        "WWW-Authenticate": 'Cookie realm="vazifalar"',
      },
    });
  }
}

export function kimlik(
  sessiyalar: SessiyaOmbori,
  apiKalitlar?: ApiKalitlar,
): RequestHandler {
  const nom = cookieNomi(sessiyalar.s);
  return async (req, _res, next) => {
    const auth = req.get("authorization");
    if (auth !== undefined && apiKalitlar !== undefined) {
      const m = /^Bearer (\S{1,200})$/.exec(auth);
      const k = m?.[1] === undefined
        ? undefined
        : await apiKalitlar.tekshir(m[1]);
      if (k === undefined) {
        throw new IlovaXatosi(401, "API kaliti yaroqsiz, " +
          "bekor qilingan yoki muddati o'tgan", {
            kod: "api-kalit-xato",
            sarlavha: "Kalit yaroqsiz",
            httpSarlavhalar: {
              "WWW-Authenticate":
                'Bearer realm="vazifalar", error="invalid_token"',
            },
          });
      }
      req.apiKalit = k;
      next();
      return;
    }
    const token = cookieOl(req.headers.cookie, nom);
    if (token !== undefined) {
      const s = await sessiyalar.tekshir(token);
      if (s !== undefined) req.sessiya = s;
    }
    next();
  };
}

export function egasiniAniqla(
  devEgasi: number | undefined,
  clsga: (egasi: number) => void,
): RequestHandler {
  return (req, _res, next) => {
    const e = req.apiKalit?.foydalanuvchiId
      ?? req.sessiya?.foydalanuvchi.id ?? devEgasi;
    if (e !== undefined) {
      req.egasi = e;
      clsga(e);
    }
    next();
  };
}

Passport bilan solishtiring: kimlik() — bizning "strategiyamiz". U ikki usulni biladi (Bearer va cookie) va natijani req.user emas, aniq turli req.sessiya / req.apiKalit ga yozadi. egasiniAniqla() so'rov egasini nestjs-cls kontekstiga ham qo'yadi — xizmatlar uni o'sha yerdan oladi. DEV rejimda (xotira yoki fayl ombori, auth yo'q) egasi — config'dagi EGASI_ID, 25–27-qismlardagi xulq.

9.4 Turlar, tokenlar, config

  • src/express.d.ts — Request ga to'rt maydon: sessiya?, egasi?, apiKalit?, xomTana? (xizmatlararo HMAC uchun xom baytlar).
  • src/tokenlar.ts — yangi DI tokenlari: AUTH, OMBOR_FABRIKASI, KURSOR_KALITI, ICHKI_SIR, SIRLAR va CLS kaliti CLS_EGASI = "egasi".
  • src/config.ts — 28-qismning sozlamalari Zod sxemasiga: sessiya muddatlari, COOKIE_SECURE, ILOVA_SIRI, AUTH_DATABASE_URL, Google, Telegram, WebAuthn, Turnstile (config validatsiyasi).
  • src/bogliqliklar.module.ts — SIRLAR (ILOVA_SIRI dan HKDF kalitlari), KURSOR_KALITI, ICHKI_SIR provider'lari; webhook xabarchisi SSRF himoyasi bilan.

9.5 Tekshiruv

Bu qadam hech narsani ulamaydi: fayllar bor, lekin ilova ularni hali chaqirmaydi. Shuning uchun tiplar toza bo'lishi kerak. Shu kesimni biz kanon fayllaridan yig'ib tekshirdik:

bash
npx prisma generate
npx tsc --noEmit; echo "exit $?"
text
✔ Generated Prisma Client (7.10.1) to .\src\generated\prisma in 1.40s
exit 0

Xato chiqsa — eng ko'p uchraydigani TS2307 (fayl qaytarilmagan). Testlarni bu darsda yurgizmaymiz. Hali commit qilmang — commit butun auth ulangach, Autentifikatsiya II darsida.

10. Real ishda

  • Nest loyihalarining ko'pida auth — Passport: LocalStrategy + JwtStrategy + global JwtAuthGuard. Kodni o'qishda birinchi qidiradigan narsa — *.strategy.ts fayllar va ularning validate() lari.
  • Ko'p provayder (Google, GitHub, Apple, korporativ SAML) — Passport eng foydali joy. Har biri uchun tayyor strategiya bor.
  • Kod review'da: "validate() bazadan foydalanuvchini tekshiryaptimi yoki faqat payload'ni qaytaryaptimi?", "algorithms berilganmi?", "sir qayerdan keladi?".
  • Intervyu: "Passport strategiyasi nima?", "validate() qaytargan qiymat qayerga tushadi?", "AuthGuard('jwt') qanday ishlaydi?".

Xulosa

  • Passport — kimlikni aniqlash usullari (strategiyalar) to'plami; Nest'da PassportStrategy(Strategy, "nom") + validate() + AuthGuard("nom").
  • validate() qaytargani req.user ga tushadi; null yoki xato — 401.
  • JwtStrategy da validate() ga tekshirilgan payload keladi; algorithms ruxsat ro'yxatini doim bering.
  • Kanon o'lchovi: Passport o'z guard'imiz bilan bir xil tezlikda, lekin +3 paket va Problem Details'siz 401 — vazifalar-api da ishlatilmaydi.
  • Loyiha: 28-qism auth mantig'i qaytdi, kimlik() va egasiniAniqla() — Express middleware; tip toza, commit hali yo'q.

Keyingi dars: Access/refresh token va cookie NestJS'da — refresh rotation va HttpOnly cookie'ni Nest'da yozamiz, keyin "chiqish"dan keyin JWT qancha yashashini o'lchaymiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Passport NestJS'da: strategiya, validate() va AuthGuard — IlmHamroh