IlmHamroh
JavaScript Full-stack/30-qism. NestJS61/65-dars20 daqiqa
Mundarija (30)

Autentifikatsiya II: Next.js va o'z NestJS backend — HttpOnly cookie, DAL, proxy va muqobillar

Qisqacha: Next.js ilovasi alohida NestJS backend bilan ishlaganda, kimlik backend'da qoladi, Next esa uni olib yuradi. Login — server action API'ga so'rov yuboradi va HttpOnly cookie'ni brauzerga o'tkazadi. Har sahifa — DAL (server-only + cache()) cookie bilan /v1/auth/men ni so'raydi. proxy.ts esa faqat optimistik yo'naltiradi (cookie bormi-yo'qmi). Backend JWT bo'lsa, Next'ga refresh qo'shiladi. Server sessiyasida refresh kerak emas — chiqish darhol ishlaydi, biz buni so'rov bilan ko'rdik. Muqobillar: Auth.js, Better Auth, Clerk. Darsning oxirida AuthModule ulanadi, testlar yuradi va 30-qismning auth bosqichi commit qilinadi.

Bu darsda

  • Next.js va alohida Nest backend o'rtasida cookie qanday yurishini sxemada chizasiz.
  • Login server action, DAL va proxy.ts ni har biri o'z vazifasi bilan yozasiz.
  • JWT backend'da token refresh Next'da qayerda va nega qiyin ekanini bilasiz.
  • Rolga asoslangan UI'ni xavfsiz qurasiz va UI'dagi yashirish himoya emasligini tushunasiz.
  • AuthModule ni ulab, testlarni yurgizib, auth bosqichini commit qilasiz.

Oldin bilishingiz kerak: @nestjs/throttler, Autentifikatsiya I: Auth.js, React'da auth holati, React cache() va Data Access Layer, Server Actions I, proxy.ts (sobiq middleware).

Olti darsda 28-qism auth'i qatlam-qatlam Nest'ga ko'chdi. Bugun ikki ish qilamiz. Avval frontend tomoniga qaraymiz: Auth.js darsida kimlik Next'ning o'zida edi, endi u alohida backend'da. Keyin oxirgi qismlarni ulaymiz, butun testlar to'plamini yurgizamiz va commit qilamiz.

1. Nega bu kerak?

Auth.js darsida Next.js hamma narsani o'zi qilardi: login sahifasi, sessiya cookie'si, foydalanuvchi bazasi. Bu kichik loyiha uchun qulay. Lekin vazifalar-api — alohida backend: uni React SPA (22-qism), Telegram bot (29-qism) va kelajakda mobil ilova ishlatadi. Kimlik bitta joyda — backend'da — bo'lishi kerak.

Shunda Next.js'ning roli o'zgaradi. U endi "qorovul" emas, "pochtachi": brauzerdan kelgan cookie'ni backend'ga olib boradi va javobni qaytaradi. Lekin pochtachi ham xato qilishi mumkin: cookie'ni JavaScript'ga ochib qo'ysa, faqat optimistik tekshiruvga ishonsa yoki rolni brauzerda hal qilsa.

O'xshatish: bank va uning filiali. Hisob raqami va imzo namunasi markaziy bankda (backend). Filial (Next.js) mijozni qabul qiladi, uning kartasini markazga yuborib tekshiradi. Filial o'zi "bu odam Aziz" deb qaror qilmaydi — faqat markaz javobini ko'rsatadi.

2.1 Ikki variant

Variant Qanday Qachon
Bir sayt (rewrite) brauzer /v1/* ni Next orqali to'g'ridan API'ga yuboradi; cookie API'niki Next va API bitta domen ostida
Server tomoni (BFF) brauzer faqat Next bilan gaplashadi; Next serveri API'ga so'rov yuboradi API ichki tarmoqda, brauzerga ko'rinmaydi

BFF (backend for frontend) — frontend uchun maxsus server qatlami. Bu yerda Next serverining o'zi. Ikkala variantda ham asosiy qoida bir xil: token JavaScript'ga ko'rinmaydi. Cookie HttpOnly, brauzer uni o'zi yuboradi.

Bu darsda BFF variantini yozamiz — Next'ning server action va DAL'i aynan shu uchun mo'ljallangan.

2.2 Oqim

sequenceDiagram
  participant B as Brauzer
  participant N as Next server
  participant A as Nest API
  B->>N: forma: kirish (server action)
  N->>A: POST /v1/auth/kirish
  A->>N: 200 + Set-Cookie __Host-sessiya
  N->>B: Set-Cookie (HttpOnly) + redirect
  B->>N: GET /vazifalar (cookie)
  N->>A: GET /v1/auth/men (Cookie)
  A->>N: 200 foydalanuvchi
  N->>B: HTML

Diagrammada cookie ikki marta "chegaradan" o'tadi: API → Next (javob sarlavhasida) va Next → brauzer (cookies().set). Keyingi har so'rovda teskari: brauzer → Next → API. JavaScript hech qayerda tokenni ko'rmaydi.

3. Login — server action

3.1 Kod

Next.js 16 server action'i (Server Actions I). Bu Next tomonidagi eskiz — biz Next ilovasini ishga tushirmadik, faqat uning API'ga yuboradigan so'rovlarini (pastda):

ts
// app/kirish/actions.ts
"use server";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

const API = process.env.API_URL!; // masalan http://api:3000

export async function kirish(_: unknown, forma: FormData) {
  const r = await fetch(`${API}/v1/auth/kirish`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      email: forma.get("email"), parol: forma.get("parol"),
    }),
    cache: "no-store",
  });
  if (!r.ok) {
    const p = (await r.json()) as { detail?: string };
    return { xato: p.detail ?? "Kirib bo'lmadi" };
  }
  const sc = r.headers.getSetCookie().find(
    (c) => c.startsWith("__Host-sessiya="));
  const token = sc?.split(";")[0]?.split("=")[1];
  if (token === undefined) return { xato: "Sessiya kelmadi" };
  (await cookies()).set("__Host-sessiya", token, {
    httpOnly: true, secure: true, sameSite: "lax", path: "/",
  });
  redirect("/vazifalar");
}

Qatorma-qator muhim joylar:

  • fetch serverda ishlaydi — brauzer API manzilini ham bilmaydi.
  • Xato bo'lsa, Problem Details'dagi detail formaga qaytadi ("Email yoki parol noto'g'ri"). Frontend kod bo'yicha ham qaror qila oladi: captcha-kerak — vidjetni ko'rsatish (@nestjs/throttler).
  • getSetCookie() — Set-Cookie sarlavhalarini massiv sifatida beradi (Node 20+, standart Headers metodi).
  • cookies().set(...) — API bergan tokenni o'sha sozlamalar bilan brauzerga qo'yadi: HttpOnly, Secure, SameSite=Lax, Path=/, domensiz. __Host- prefiksi aynan shuni talab qiladi (Cookie chuqur).
  • Javob { mfa: "kerak" } bo'lsa (2FA yoqilgan) — haqiqiy kodda ikkinchi qadam formasi ko'rsatiladi. Eskizda bu tarmoq yo'q.

3.2 API nima qaytaradi — biz ishga tushirdik

Next server action'i qiladigan so'rovlarni oddiy Node skriptida, jonli vazifalar-api ga yubordik (dist/main.js, SAQLASH=postgres, vazifalar_api va vazifalar_auth rollari, PostgreSQL 17.6). sorovId va vaqtlarni … bilan almashtirdik. Chiqishda statuslar ketma-ketligini kuzating: 401 → 200 → 403 → 200 → 204 → 401. Har biri bitta qatlamning javobi:

text
DAL (cookie'siz) → 401 {"type":"https://vazifalar-api.example/xatolar/kirish-kerak","title":"Kirish kerak","status":401,"detail":"Avval tizimga kiring","instance":"/v1/auth/men","kod":"kirish-kerak","sorovId":"…"}
kirish aziz@example.com → 200
  Set-Cookie: __Host-sessiya=<token>; Path=/; HttpOnly; Secure; SameSite=Lax
DAL (Aziz) → 200 {"foydalanuvchi":{"id":1,"email":"aziz@example.com","toliqIsm":"Aziz Karimov"},"sessiya":{"id":1,"eslabQol":false,"yaratilgan":"…","tugash":"…"}}
GET /v1/admin/foydalanuvchilar (Aziz) → 403
kirish bekzod@example.com → 200
  Set-Cookie: __Host-sessiya=<token>; Path=/; HttpOnly; Secure; SameSite=Lax
GET /v1/admin/foydalanuvchilar (Bekzod — admin) → 200
chiqish (Aziz) → 204
DAL (Azizning eski cookie'si) → 401 {"type":"https://vazifalar-api.example/xatolar/kirish-kerak","title":"Kirish kerak","status":401,"detail":"Avval tizimga kiring","instance":"/v1/auth/men","kod":"kirish-kerak","sorovId":"…"}

Oxirgi qatorni access/refresh darsidagi 8-qator bilan solishtiring. U yerda chiqqan foydalanuvchining JWT'si 200 olgan edi. Bu yerda sessiya bekor — keyingi so'rov darhol 401. Next tomonida hech qanday qo'shimcha kod kerak bo'lmadi.

Set-Cookie da Expires yo'q — "meni eslab qol" belgilanmagan: brauzer yopilganda cookie o'chadi, bazada esa sessiya 12 soatlik idle muddat bilan (Session vs JWT qarori).

4. DAL — haqiqiy tekshiruv joyi

Data Access Layer darsida qoida bor edi: ruxsat tekshiruvi ma'lumot yonida, bitta server modulida. Endi DAL'ning "ma'lumoti" — backend javobi:

ts
// lib/dal.ts
import "server-only";
import { cache } from "react";
import { cookies } from "next/headers";

export type Men = { id: number; email: string; toliqIsm: string };

export const sessiya = cache(async (): Promise<Men | null> => {
  const c = (await cookies()).get("__Host-sessiya");
  if (c === undefined) return null;
  const r = await fetch(`${process.env.API_URL!}/v1/auth/men`, {
    headers: { Cookie: `__Host-sessiya=${c.value}` },
    cache: "no-store",
  });
  if (!r.ok) return null; // 401 — sessiya bekor yoki muddati o'tgan
  const j = (await r.json()) as { foydalanuvchi: Men };
  return j.foydalanuvchi;
});
  • server-only — bu modul brauzer to'plamiga tushsa, build yiqiladi.
  • cache() — bitta render davomida layout, sahifa va komponent sessiya() ni uch marta chaqirsa ham, API'ga bitta so'rov.
  • cache: "no-store" — Next'ning fetch keshi sessiya javobini hech qachon saqlamasin. Aks holda bir foydalanuvchining javobi boshqasiga ketishi mumkin.
  • Ma'lumot olish ham shu modulda: vazifalar() funksiyasi o'sha cookie bilan /v1/vazifalar ni so'raydi. Backend egalikni o'zi tekshiradi (RLS) — Next'da WHERE yo'q.

Tekshirib ko'ring: DAL'da cache: "no-store" unutilsa, eng yomon holatda nima bo'ladi?

Javob

Next serveri fetch javobini keshlab qo'yishi mumkin. Keyingi so'rov boshqa cookie bilan kelsa ham, kesh kaliti (URL) bir xil bo'lsa — eski javob qaytadi: Dilnoza Azizning profilini ko'radi. Sessiyaga bog'liq har so'rov keshdan tashqarida bo'lishi kerak. Backend tomonida vazifalar-api buning uchun /v1/auth javoblariga Cache-Control: no-store qo'yadi — lekin Next'ning o'z keshi uchun cache: "no-store" baribir kerak.

5. proxy.ts — optimistik yo'naltirish

proxy.ts darsida aytgandik: proxy so'rovni sahifaga yetmasdan ko'radi, lekin har so'rovda API'ga borish uning ishi emas. Shuning uchun u faqat optimistik tekshiradi:

ts
// proxy.ts
import { NextResponse, type NextRequest } from "next/server";

export function proxy(req: NextRequest) {
  const bor = req.cookies.has("__Host-sessiya");
  if (!bor) {
    const url = new URL("/kirish", req.url);
    url.searchParams.set("keyin", req.nextUrl.pathname);
    return NextResponse.redirect(url);
  }
  return NextResponse.next();
}

export const config = {
  matcher: ["/vazifalar/:path*", "/sozlamalar"],
};

"Optimistik" — cookie bor bo'lsa o'tkazadi, lekin uning haqiqiyligini tekshirmaydi. Eskirgan yoki soxta cookie bilan ham sahifa ochiladi — va DAL 401 oladi. Ya'ni proxy — qulaylik (kirmagan odam darhol login sahifasini ko'radi), himoya — DAL va backend. matcher aniq yo'llar bilan: rasm va CSS so'rovlari proxy'dan o'tmasin.

Endi Next tomonidagi uch qatlam va backend'ni bitta jadvalga qo'yamiz. Har qatorda — qatlam nimani biladi va xato bo'lsa nima qiladi:

Qatlam Nimani tekshiradi API'ga boradimi Rad etsa
proxy.ts cookie bormi yo'q /kirish ga yo'naltiradi
DAL (sessiya()) cookie haqiqiymi (/v1/auth/men) ha, render'da bir marta null → sahifa "kiring" deydi
server action forma, keyin API javobi ha formaga detail qaytaradi
Nest backend sessiya, rol, egalik, tezlik — 401 / 403 / 404 / 429

Pastga tushgan sari tekshiruv qimmatlashadi va ishonchliroq bo'ladi. Faqat oxirgi qator haqiqiy himoya: qolganlari foydalanuvchiga tezroq va tushunarliroq javob berish uchun.

6. Token refresh — agar backend JWT bo'lsa

6.1 Qayerda yangilash

Backend access/refresh sxemasida bo'lsa, Next'ga uchinchi vazifa qo'shiladi: access tugaganda uni yangilash. Ikki joy bor va ikkalasining ham tuzog'i bor:

  • DAL'da — /men 401 bersa, refresh bilan /auth/yangila ni chaqirib, yangi cookie qo'yish. Lekin Server Component render paytida cookie yoza olmaydi — faqat server action, Route Handler yoki proxy yozadi.
  • proxy.ts da — har so'rovda access muddatini tekshirib, tugayotgan bo'lsa yangilash. Ishlaydi, lekin proxy har sahifa uchun API'ga boradi.

6.2 Poyga

Eng nozik joy — bir vaqtda kelgan so'rovlar. Sahifa ochilganda Next bir nechta so'rovni parallel bajaradi. Access tugagan bo'lsa, hammasi bir vaqtda o'sha refresh bilan yangilashga urinadi. Birinchisi o'tadi, qolganlari esa ishlatilgan refresh'ni yuboradi — reuse detection butun oilani o'ldiradi (access/refresh darsidagi 3-mashq). Foydalanuvchi sababsiz chiqib ketadi.

Yechimlar ma'lum: yangilashni bitta joyda navbatga qo'yish, qisqa grace oynasi, yoki access'ni muddatidan oldin yangilash. Hammasi — qo'shimcha kod va qo'shimcha xavf. Server sessiyasida bu bo'limning o'zi yo'q: idle muddat bazada har so'rovda suriladi, cookie esa o'zgarmaydi.

7. Rolga asoslangan UI

Admin menyusini faqat adminga ko'rsatish kerak. Ikki qoida:

  1. UI'dagi yashirish — himoya emas. Hujumchi tugmasiz ham POST /v1/admin/foydalanuvchilar/2/bloklash yuboradi. Himoya — backend'dagi @Rollar("admin"). Biz yuqorida ko'rdik: Aziz — 403, Bekzod — 200.
  2. Rol — serverdan. Rolni localStorage dan yoki o'zi yasagan cookie'dan o'qish — foydalanuvchi uni o'zgartiradi.

Halol eslatma: vazifalar-api ning /v1/auth/men javobida rol yo'q (yuqoridagi chiqishga qarang). Bu xavf emas — frontend'ga shu paytgacha kerak bo'lmagan. Ikki yo'l bor: /men ga rol maydonini qo'shish (kichik o'zgarish, 3-mashq) yoki admin sahifasini ochganda /v1/admin/... ni so'rab, 403 bo'lsa "ruxsat yo'q" ko'rsatish. Birinchisi menyu uchun qulayroq.

8. Muqobillar

Yechim Kimlik qayerda Next tomoni Nest tomoni
O'z backend (bu dars) Nest + Postgres server action, DAL, proxy 28-qism auth
Auth.js Next auth() — Credentials provayderi Nest'ni chaqiradi token yoki sessiyani tekshiradi
Better Auth kutubxona (Next yoki Nest'da) @better-auth/react mijozi AuthModule.forRoot (jamiyat paketi)
Clerk bulut xizmati (SaaS) tayyor komponentlar, middleware Clerk JWT'sini JWKS bilan tekshiradi
  • Auth.js — Auth.js darsida o'rgangansiz. Alohida backend bilan u ikkinchi sessiya qatlamini qo'shadi: Next'da Auth.js sessiyasi, backend'da o'zinikiga. Ikki joyda chiqish, ikki muddat — sinxronlash kerak.
  • Better Auth — OAuth va tayyor yechimlar darsida ko'rdik: Nest moduli global guard bilan. Next uchun React mijozi bor. Kimlik bitta joyda, lekin kutubxonaning sxemasida.
  • Clerk — foydalanuvchilar, login sahifalari, 2FA — hammasi Clerk serverida. Next'ga tayyor komponentlar, backend'ga — imzolangan qisqa JWT, uni ochiq kalit (JWKS) bilan tekshirasiz. Tez, lekin foydalanuvchi ma'lumoti va narx — tashqi xizmatda (Auth kutubxonalari va xizmatlari). Biz Clerk'ni sinamadik.

vazifalar-api uchun tanlov o'zgarmaydi: kimlik backend'da, Next — pochtachi.

9. Hujumchi nigohi

  1. Tokenni JavaScript'ga berish. Server action tokenni { token } qilib qaytarib, mijoz localStorage ga yozsa — bitta XSS hammasini oladi. Token faqat HttpOnly cookie'da.
  2. Faqat proxy'ga ishonish. Soxta __Host-sessiya=x cookie bilan proxy o'tkazadi. Agar sahifa DAL'siz ma'lumot ko'rsatsa — ochiq.
  3. Keshlangan sessiya javobi. cache: "no-store" siz DAL — boshqa foydalanuvchining profili.
  4. Rol brauzerdan. localStorage.rol = "admin" — admin menyusi ochiladi. Zarar faqat UI'da bo'lishi uchun backend har amalni o'zi tekshiradi.
  5. Server action'ni "yashirin" deb o'ylash. Server action — ommaviy POST yo'li (Server Actions xavfsizligi). Unga ham tezlik cheklovi va CAPTCHA kerak — ular backend'da (CheklovGuard), Next faqat xatoni ko'rsatadi.

10. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Login 200, lekin keyingi sahifa kirmagan Set-Cookie brauzerga o'tkazilmagan (server-to-server javobida qolgan) cookies().set(...)
__Host- cookie qabul qilinmaydi Secure yo'q, Path / emas yoki Domain bor aynan Secure; Path=/, domensiz
Profil boshqa foydalanuvchiniki DAL fetch keshlangan cache: "no-store"
Render paytida "Cookies can only be modified…" Server Component'da cookie yozish server action, Route Handler yoki proxy
Admin menyusi adminga ham ko'rinmaydi /men da rol yo'q /men ga rol qo'shing yoki admin yo'lini so'rang

11. Mashqlar

Mashqlar kurs/mashqlar/30/61-auth-ii/ da.

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

  • Modul brauzer to'plamiga tushmasligi uchun import — import ""
  • Bitta render davomida bir martalik chaqiruv — React ()
  • Set-Cookie sarlavhalarini massiv qilib beradigan metod — r.headers.()
  • Faqat cookie borligini tekshiradigan qatlam —
  • Chiqqan foydalanuvchining eski cookie'si bilan /v1/auth/men —
Yechim
  • server-only — brauzer kodiga import qilinsa build xatosi.
  • cache — react dan; so'rovlar orasida emas, faqat bitta render ichida.
  • getSetCookie — oddiy get("set-cookie") bir nechta cookie'ni bitta satrga qo'shib yuboradi.
  • proxy.ts — optimistik; haqiqiy tekshiruv — DAL.
  • 401 — server sessiyasi darhol bekor.

Server action API javobidan sessiya tokenini olishi kerak. sessiyaOl(setCookieMassivi) ni yozing: __Host-sessiya= bilan boshlanganini topsin, token va atributlarni (HttpOnly, Secure, SameSite, Path) qaytarsin; topilmasa — null. Ishora: find, split, toLowerCase.

Yechim
js
function sessiyaOl(sarlavhalar) {
  const c = sarlavhalar.find((s) => s.startsWith("__Host-sessiya="));
  if (!c) return null;
  const [juft, ...qismlar] = c.split(";").map((x) => x.trim());
  const atr = {};
  for (const q of qismlar) {
    const [k, v] = q.split("=");
    atr[k.toLowerCase()] = v ?? true;
  }
  return { token: juft.slice("__Host-sessiya=".length), atr };
}

const javob = [
  "boshqa=1; Path=/",
  "__Host-sessiya=abc123; Path=/; HttpOnly; Secure; SameSite=Lax",
];
console.log(sessiyaOl(javob));
console.log(sessiyaOl(["boshqa=1"]));

Konsolda:

text
{
  token: 'abc123',
  atr: { path: '/', httponly: true, secure: true, samesite: 'Lax' }
}
null

Atributlarni o'qish — keyin cookies().set(...) ga xuddi shu qiymatlarni berish uchun. Agar API Expires yuborsa ("meni eslab qol"), uni ham o'tkazish kerak — aks holda cookie brauzer yopilganda o'chadi.

3-mashq (qiyin): rolga asoslangan menyu

Backend'da /men ga rol qo'shildi deylik. menyu(men) funksiyasini yozing: null — ["Kirish"]; oddiy foydalanuvchi — ["Vazifalar", "Sozlamalar", "Chiqish"]; admin — qo'shimcha "Foydalanuvchilar" (Sozlamalardan keyin). Keyin "hujumchi" { ...men, rol: "admin" } yasaganda menyu o'zgarsa ham, backend nimaga javob berishini ko'rsating: server(rol) — haqiqiy rolga qarab admin yo'liga 200 yoki 403. Ishora: spread.

Yechim
js
function menyu(men) {
  if (men === null) return ["Kirish"];
  const m = ["Vazifalar", "Sozlamalar"];
  if (men.rol === "admin") m.push("Foydalanuvchilar");
  return [...m, "Chiqish"];
}
const BAZA = new Map([[1, "foydalanuvchi"], [4, "admin"]]);
const server = (id) => (BAZA.get(id) === "admin" ? 200 : 403);

const aziz = { id: 1, rol: "foydalanuvchi" };
console.log(menyu(null));
console.log(menyu(aziz));
const soxta = { ...aziz, rol: "admin" }; // brauzerda o'zgartirildi
console.log(menyu(soxta), "→ server:", server(soxta.id));
console.log(menyu({ id: 4, rol: "admin" }), "→ server:", server(4));

Konsolda:

text
[ 'Kirish' ]
[ 'Vazifalar', 'Sozlamalar', 'Chiqish' ]
[ 'Vazifalar', 'Sozlamalar', 'Foydalanuvchilar', 'Chiqish' ] → server: 403
[ 'Vazifalar', 'Sozlamalar', 'Foydalanuvchilar', 'Chiqish' ] → server: 200

Uchinchi qator — darsning asosiy xulosasi. Hujumchi menyuni o'zgartira oladi, lekin server rolni o'z bazasidan oladi — 403. UI rolni faqat "nimani ko'rsatish" uchun ishlatadi. Bu jonli serverdagi Aziz va Bekzodning 403/200 javobining aynan o'zi.

12. Loyiha qadami: AuthModule, testlar va commit

12.1 AuthModule.forRoot

Oxirgi bo'lak — hammasini ulaydigan dinamik modul. Kanon faylidan, asosiy qism (bo'lingan, izohlar qisqartirildi):

ts
export type AuthModulSozlamasi = {
  yoqilgan: boolean; auth?: AuthBogliqliklari | undefined;
};

@Global()
@Module({})
export class AuthModule {
  static forRoot(
    { yoqilgan, auth }: AuthModulSozlamasi): DynamicModule {
    const authProvider: Provider =
      auth !== undefined ? { provide: AUTH, useValue: auth }
      : !yoqilgan ? { provide: AUTH, useValue: null }
      : {
        provide: AUTH,
        inject: [SAQLASH, CONFIG, LOG, SIRLAR],
        useFactory: (s: Saqlash, c: Config, log: Log,
          sirlar: Sirlar): AuthBogliqliklari | null => {
          if (s.pool === undefined) return null;
          const pool = c.AUTH_DATABASE_URL === undefined ? s.pool
            : pgPool(c.AUTH_DATABASE_URL, { max: 5,
              application_name: "vazifalar-api-auth",
              idleTimeoutMillis: 30_000,
              connectionTimeoutMillis: 5_000,
              statement_timeout: c.PG_STATEMENT_TIMEOUT });
          if (pool !== s.pool) ozimizniki.add(pool);
          if (pool !== s.pool) pool.on("error", (err) => {
            log.error({ err },
              "pg pool (auth): bo'sh ulanish uzildi");
          });
          return authYarat(pool, c, sirlar, log);
        },
      };
    return {
      module: AuthModule,
      controllers: yoqilgan
        ? [AuthController, MfaController, AdminController] : [],
      providers: [authProvider, AuthYopuvchi,
        ...(yoqilgan ? [AuthService] : [])],
      exports: [AUTH],
    };
  }
}

Uch holat:

  • Testlar tayyor auth beradi — useValue.
  • DEV rejim (xotira, fayl) — AUTH = null, controller'lar ro'yxatdan o'tmaydi: /v1/auth/* — 404, 28-qismdagidek.
  • Postgres/Prisma — fabrika. Auth uchun alohida pool vazifalar_auth roli bilan (AUTH_DATABASE_URL), ma'lumot uchun — vazifalar_api (28-qismdagi ikki rol).

AuthYopuvchi — lifecycle hook lar bilan: beforeApplicationShutdown da fondagi xatlar tugashini kutadi, onApplicationShutdown da faqat o'zi ochgan pool'ni yopadi (ozimizniki — WeakSet). Test bergan pool'ni yopmaydi — u test faylniki.

12.2 AppModule — guard'lar tartibi

app.module.ts da AuthModule.forRoot({ yoqilgan: authYoqilgan, auth }) importlarga qo'shiladi va global enhancer'lar ro'yxati shunday bo'ladi (kanondan):

ts
providers: [
  ServisImzoGuard,
  { provide: APP_PIPE, useClass: SxemaPipe },
  { provide: APP_GUARD, useClass: KirishGuard },
  { provide: APP_GUARD, useClass: CheklovGuard },
  { provide: APP_GUARD, useClass: CaptchaGuard },
  { provide: APP_GUARD, useClass: JsonTanaGuard },
  { provide: APP_FILTER, useClass: ProblemDetailsFilter },
],

Global guard darsidagi tartib: kim → tezlik → CAPTCHA → 415. authYoqilgan — config'dan: tayyor auth berilgan yoki SAQLASH postgres/prisma. Controller ro'yxati statik bo'lishi kerakligi uchun config bu yerda sinxron o'qiladi.

12.3 Ochiq xatolar 4 va 5

Ochiq xato 4 — .env.example superuser bilan edi: DATABASE_URL=postgres://postgres@…. Superuser RLS'ni chetlab o'tadi (BYPASSRLS): set_config unutilgan bitta so'rov — hamma foydalanuvchining ma'lumoti. Namuna fayldan nusxa olgan dasturchi himoyasiz ishga tushardi. Endi:

text
# DATABASE_URL=postgres://vazifalar_api@127.0.0.1:5495/vazifalar
# AUTH_DATABASE_URL=postgres://vazifalar_auth@127.0.0.1:5495/vazifalar

Test .env.example ni o'qiydi va tekshiradi: rollar to'g'ri, parol yo'q, birorta qatorda postgres://postgres yo'q. Biz yurgizdik:

text
✓ test/config.test.ts > .env.example: ochiq qiymatlar config'dan o'tadi; izohdagi DATABASE_URL — vazifalar_api, AUTH_DATABASE_URL — vazifalar_auth (superuser emas) 39ms

Ochiq xato 5 — test nomi "5 000 darajali chuqur obyekt" edi, kod esa repeat(2_500). Kichik narsa, lekin test nomi — hujjat: o'qigan odam noto'g'ri xulosa chiqaradi. Hisob — brauzerda ham:

js
const chuqur = (n) => `${'{"a":'.repeat(n)}1${"}".repeat(n)}`;
const bayt = (s) => new TextEncoder().encode(s).length;
console.log("2 500 daraja:", bayt(chuqur(2_500)), "bayt");
console.log("5 000 daraja:", bayt(chuqur(5_000)), "bayt");
console.log("16 KB:", 16 * 1024, "bayt");

Konsolda:

text
2 500 daraja: 15001 bayt
5 000 daraja: 30001 bayt
16 KB: 16384 bayt

5 000 darajali obyekt 16 KB limitidan katta — u 413 olardi va "chuqur JSON" holatini umuman tekshirmasdi. Endi nom "2 500 darajali (15 001 bayt)", bayt soni esa assert bilan qotirilgan. Biz yurgizgan test:

text
✓ test/hujum.test.ts > katta va chuqur JSON: 16 KB dan katta — 413; 2 500 darajali chuqur obyekt (15 001 bayt — 16 KB ichida) — 422, server yiqilmaydi 2675ms

12.4 Testlar

test/ papkasi kanon holatiga keladi: 28-qismning 26 ta test fayli (Vitest'da), yangi test/nest-auth.test.ts (9 test — oldingi darslardagi to'rt xatoning regressiyasi) va e2e/ (Playwright: CORS, CSRF, passkey — haqiqiy brauzer). Ko'chishda test infratuzilmasida ikki narsa o'zgardi:

  • 28-qism testlari ilovaYarat() ni sinxron Express ilovasi deb ishlatardi. Nest'da u async — await qo'shildi.
  • supertest(app.getHttpServer()) parallel so'rovlarda address() null xatosini berdi: bitta Server obyekti yopilib qoladi. Endi testlarga Express nusxasi beriladi — getHttpAdapter().getInstance() (expressIlova yordamchisi).

Biz o'z PostgreSQL 17.6 klasterimizda auth bilan bog'liq olti faylni yurgizdik:

text
 Test Files  6 passed (6)
      Tests  46 passed (46)

(nest-auth, cheklov, otp, oidc, config, hujum.) Kanon esa butun to'plamni yurgizgan:

Qadam Natija
tip / lint / build exit 0 / exit 0 / exit 0
Vitest (47 fayl) 272 o'tdi, 1 skip (Testcontainers — alohida 10/10)
e2e (Playwright, haqiqiy brauzer) 10/10
migratsiyalar 14; migrate diff bo'sh; sql/ bilan farq 0 qator
jonli, uch rejim 200×18, 201×3, 202×6, 204×6, 401×9, 404×6, 409×3, 412×3

Halol eslatma: kanon yugurishida bitta fayl darajasidagi xato bo'ldi — migratsiya.test ning tayyorlov qismi 30 soniyada tugamadi (mashinada boshqa ish parallel ketayotgan edi). Alohida yurgizilganda — 2/2, 0.75 s. Bu yuk sababli beqarorlik, kod xatosi emas; sizda ham shunday bo'lsa — faylni alohida yurgizing.

12.5 Tekshiruv va commit

bash
npm run check
npm run e2e

Kesimni biz ham kanon fayllaridan yig'ib tekshirdik — oldingi darslardagi to'rt xato yo'qoldi:

text
exit 0

(npx tsc --noEmit.) Bu kesim yetti darsdan beri birinchi marta yashil. Commit'dan oldin yetti darsda nima qo'shilganini bir qarashda ko'rib chiqing:

Dars Loyihaga nima qo'shildi
Passport NestJS'da 28-qism auth fayllari nest tarmog'iga qaytdi
Access/refresh token AuthService.sessiyaBer, CSRF va kimlik() middleware'lari; idempotentlik 401 ni saqlamaydi
Global auth guard KirishGuard, @Ochiq/@SessiyaKerak/@Rollar, AdminController; versiyasiz yo'llar yopildi, /health ochildi
CASL OMBOR_FABRIKASI + CLS; SSE va eksport egasi bilan
OAuth auth controller'lari; ochiq xato 2 va 3
@nestjs/throttler CheklovGuard, CaptchaGuard; ochiq xato 1
shu dars AuthModule.forRoot, guard tartibi, testlar; ochiq xato 4 va 5

Endi — 30-qism auth bosqichining commit'i:

bash
git add -A
M="feat(api): 28-qism auth NestJS'da — global guard, sessiya,"
M+=" CSRF, RBAC+RLS, API kalit, OIDC, 2FA/passkey, CheklovGuard;"
M+=" 28-qism ochiq xatolari 1–5"
git commit -m "$M"

13. Real ishda

  • Next.js + alohida backend (Nest, Go, Java) — o'rta va katta kompaniyalarda keng tarqalgan. Frontend jamoasi "BFF"ni, backend jamoasi — auth'ni egallaydi.
  • Kod review'da Next tomonidagi birinchi savollar: "token qayerda?", "DAL keshlanmaydimi?", "proxy faqat optimistikmi?".
  • SaaS auth (Clerk, Auth0) — startaplarda tez boshlash uchun; ma'lumot va narx tashqarida.
  • Intervyu: "Next.js'da alohida API bilan auth'ni qanday qurasiz?", "proxy (middleware) da auth tekshirish yetarlimi?", "Rolga asoslangan UI xavfsizmi?".

Xulosa

  • Alohida backend bilan kimlik backend'da; Next — pochtachi: server action cookie'ni o'tkazadi, DAL tekshiradi, proxy faqat optimistik.
  • Token JavaScript'ga ko'rinmaydi: HttpOnly __Host-sessiya; DAL — server-only, cache(), cache: "no-store".
  • JWT backend'da Next'ga refresh va poyga muammosi qo'shiladi; server sessiyasida chiqish darhol — biz ko'rdik: 401.
  • Rolga asoslangan UI — faqat ko'rinish; himoya — backend @Rollar. Kanon /men da rol yo'q — qo'shish oson.
  • Loyiha: AuthModule.forRoot (testlar / DEV / Postgres), guard tartibi, ochiq xato 4–5, testlar o'tdi — auth bosqichi commit qilindi.

Keyingi dars: Fayl yuklash NestJS'da — 29-qism integratsiyalari Nest'ga ko'chishni boshlaydi: Multer FileInterceptor ni o'rganamiz va kanon nega oqimli yuklashda qolganini o'lchaymiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Autentifikatsiya II: Next.js va o'z NestJS backend — HttpOnly cookie, DAL, proxy va muqobillar — IlmHamroh