Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. Arxitektura: cookie qayerda yuradi
- 2.1 Ikki variant
- 2.2 Oqim
- 3. Login — server action
- 3.1 Kod
- 3.2 API nima qaytaradi — biz ishga tushirdik
- 4. DAL — haqiqiy tekshiruv joyi
- 5. proxy.ts — optimistik yo'naltirish
- 6. Token refresh — agar backend JWT bo'lsa
- 6.1 Qayerda yangilash
- 6.2 Poyga
- 7. Rolga asoslangan UI
- 8. Muqobillar
- 9. Hujumchi nigohi
- 10. Ko'p uchraydigan xatolar
- 11. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): Set-Cookie'ni o'qish
- 3-mashq (qiyin): rolga asoslangan menyu
- 12. Loyiha qadami: AuthModule, testlar va commit
- 12.1 AuthModule.forRoot
- 12.2 AppModule — guard'lar tartibi
- 12.3 Ochiq xatolar 4 va 5
- 12.4 Testlar
- 12.5 Tekshiruv va commit
- 13. Real ishda
- Xulosa
- Manbalar
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/menni so'raydi.proxy.tsesa 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 oxiridaAuthModuleulanadi, 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.tsni 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.
AuthModuleni 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. Arxitektura: cookie qayerda yuradi
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: HTMLDiagrammada 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):
// 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:
fetchserverda ishlaydi — brauzer API manzilini ham bilmaydi.- Xato bo'lsa, Problem Details'dagi
detailformaga qaytadi ("Email yoki parol noto'g'ri"). Frontendkodbo'yicha ham qaror qila oladi:captcha-kerak— vidjetni ko'rsatish (@nestjs/throttler). getSetCookie()—Set-Cookiesarlavhalarini massiv sifatida beradi (Node 20+, standartHeadersmetodi).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:
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:
// 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 komponentsessiya()ni uch marta chaqirsa ham, API'ga bitta so'rov.cache: "no-store"— Next'ningfetchkeshi 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/vazifalarni so'raydi. Backend egalikni o'zi tekshiradi (RLS) — Next'daWHEREyo'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:
// 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 —
/men401 bersa, refresh bilan/auth/yangilani 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:
- UI'dagi yashirish — himoya emas. Hujumchi tugmasiz ham
POST /v1/admin/foydalanuvchilar/2/bloklashyuboradi. Himoya — backend'dagi@Rollar("admin"). Biz yuqorida ko'rdik: Aziz — 403, Bekzod — 200. - Rol — serverdan. Rolni
localStoragedan 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
- Tokenni JavaScript'ga berish. Server action tokenni
{ token }qilib qaytarib, mijozlocalStoragega yozsa — bitta XSS hammasini oladi. Token faqat HttpOnly cookie'da. - Faqat proxy'ga ishonish. Soxta
__Host-sessiya=xcookie bilan proxy o'tkazadi. Agar sahifa DAL'siz ma'lumot ko'rsatsa — ochiq. - Keshlangan sessiya javobi.
cache: "no-store"siz DAL — boshqa foydalanuvchining profili. - Rol brauzerdan.
localStorage.rol = "admin"— admin menyusi ochiladi. Zarar faqat UI'da bo'lishi uchun backend har amalni o'zi tekshiradi. - 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-Cookiesarlavhalarini 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 —
reactdan; 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.
2-mashq (o'rta): Set-Cookie'ni o'qish
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
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:
{
token: 'abc123',
atr: { path: '/', httponly: true, secure: true, samesite: 'Lax' }
}
nullAtributlarni 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
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:
[ 'Kirish' ]
[ 'Vazifalar', 'Sozlamalar', 'Chiqish' ]
[ 'Vazifalar', 'Sozlamalar', 'Foydalanuvchilar', 'Chiqish' ] → server: 403
[ 'Vazifalar', 'Sozlamalar', 'Foydalanuvchilar', 'Chiqish' ] → server: 200Uchinchi 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):
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
authberadi —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_authroli 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):
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:
# DATABASE_URL=postgres://vazifalar_api@127.0.0.1:5495/vazifalar
# AUTH_DATABASE_URL=postgres://vazifalar_auth@127.0.0.1:5495/vazifalarTest .env.example ni o'qiydi va tekshiradi: rollar to'g'ri, parol yo'q, birorta qatorda postgres://postgres yo'q. Biz yurgizdik:
✓ test/config.test.ts > .env.example: ochiq qiymatlar config'dan o'tadi; izohdagi DATABASE_URL — vazifalar_api, AUTH_DATABASE_URL — vazifalar_auth (superuser emas) 39msOchiq 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:
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:
2 500 daraja: 15001 bayt
5 000 daraja: 30001 bayt
16 KB: 16384 bayt5 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:
✓ 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 2675ms12.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 uasync—awaitqo'shildi. supertest(app.getHttpServer())parallel so'rovlardaaddress() nullxatosini berdi: bittaServerobyekti yopilib qoladi. Endi testlarga Express nusxasi beriladi —getHttpAdapter().getInstance()(expressIlovayordamchisi).
Biz o'z PostgreSQL 17.6 klasterimizda auth bilan bog'liq olti faylni yurgizdik:
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
npm run check
npm run e2eKesimni biz ham kanon fayllaridan yig'ib tekshirdik — oldingi darslardagi to'rt xato yo'qoldi:
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:
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/menda 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
- Next.js hujjati — Authentication guide (DAL, server actions, optimistik tekshiruv): https://nextjs.org/docs/app/guides/authentication
- Next.js hujjati —
cookies(): https://nextjs.org/docs/app/api-reference/functions/cookies - Next.js hujjati — proxy.ts: https://nextjs.org/docs/app/api-reference/file-conventions/proxy
- MDN —
Headers.getSetCookie(): https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie - MDN — Cookie prefikslari (
__Host-): https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes - Clerk hujjati — manual JWT verification: https://clerk.com/docs/backend-requests/manual-jwt
- Kanon (IlmHamroh, 2026-11-01):
AuthModule,AppModule, ochiq xato 4–5, auth bosqichining yakuniy tekshiruv hisoboti. Jonli API'ga "server action / DAL" so'rovlari, auth testlari (o'z PostgreSQL 17.6 klasterimizda) va yakuniytsckesimi — biz ishga tushirdik (2026-11-02, NestJS 12.2.0, Node 24.21.0). Next.js tomonidagi kod — eskiz, ishga tushirilmagan.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!