WisarWisar
Dasturlash kitobi/5-QISM — Nodejs24 daqiqa

5.15-bob: Autentifikatsiya — session, cookie, JWT, bcrypt

5-QISM — Node.js Backend · 15-mavzu


1. Kirish va motivatsiya

Endi backend'ning eng muhim, eng ko'p so'raladigan mavzusiga keldik: autentifikatsiya (authentication) — foydalanuvchi kimligini aniqlash. Har bir real ilovada bu bor: ro'yxatdan o'tish, login, "tizimga kirgan holda qolish", maxfiy sahifalar. Intervyularda ham, ishda ham — auth bilmasdan backend dasturchi bo'lib bo'lmaydi.

Bu mavzu — xavfsizlikning (14) markazi. Bitta xato — minglab foydalanuvchi paroli o'g'irlanishiga olib keladi (real falokat, yangiliklar sarlavhasi). Shuning uchun bu yerda "qanday ishlaydi" dan ham muhimrog'i — "nega aynan shunday, va nima xavfli" ni tushunish. Biz boshidan, eng sodda (va xato) yo'ldan boshlab, nega professional yechimlar (bcrypt, JWT, xavfsiz cookie) kerakligini bosqichma-bosqich ko'ramiz.

Asosiy ikki masala bor. Birinchi: parolni qanday xavfsiz saqlash (hech kim, hatto DB'ni ko'rgan dasturchi ham, parolni bilmasin) — bu bcrypt. Ikkinchi: HTTP holatsiz (stateless — 0.4) bo'lsa, foydalanuvchi qanday "kirgan holda qoladi" (har so'rovda qayta parol so'ramasdan) — bu session/cookie yoki JWT.

O'xshatish 1 (autentifikatsiya): auth — klubga kirish. Eshikda kimligingizni tasdiqlaysiz (pasport — login/parol). Tasdiqlangach, qo'lingizga bilaguzuk (cookie/token) bog'lashadi. Endi har safar barda yoki zalda pasport ko'rsatmaysiz — bilaguzuk yetarli (har so'rovda token). Bilaguzuk — "men allaqachon tekshirilganman" degan isbot.

O'xshatish 2 (hashing): parolni ochiq saqlash — uy kalitini eshik tagiga qo'yish (har kim oladi). Hashing — kalitni qaytmas ko'rinishga aylantirib saqlash: hatto o'g'ri DB'ni ko'rsa ham, asl parolni tiklay olmaydi.

Nega muhim?

  • Har ilovada bor — login/ro'yxat har joyda; bu — asos.
  • Xavfsizlik markazi (14) — xato qimmatga tushadi (parol o'g'irlash).
  • Intervyu/ish talabi — auth — backend dasturchining majburiy bilimi.
  • Keyingi bo'limlar — refresh token 5.16-bob, RBAC 5.17-bob, OTP 5.18-bob — hammasi shu asosga quriladi.

2. Nazariya — chuqur tushuntirish

2.1. Authentication vs Authorization (ikki xil "auth")

Ikki tushuncha tez-tez aralashtiriladi:

text
  Authentication (autentifikatsiya) — "SIZ KIMSIZ?"
     kimligini tasdiqlash (login + parol  "ha, bu Ali")

  Authorization (avtorizatsiya) — "SIZGA NIMAGA RUXSAT BOR?"
     ruxsatlarni tekshirish (Ali admin'mi? bu sahifani ko'rolladimi?)

Tartib: avval authentication (kimligini aniqlash — bu bob), keyin authorization (ruxsat — 5.17: RBAC). Avval "kim", keyin "nimaga haqli". Bu bobda authentication + uni saqlab turish (session/JWT).

2.2. Sodda (XATO) yo'l — nega ishlamaydi

Yangi dasturchi auth'ni shunday yozadi: parolni DB'ga ochiq (plain text) saqlash:

text
  users jadvali:
  | id | email      | parol      |
  | 1  | ali@a.uz   | parol123   |    OCHIQ! (FALOKAT)

Nega falokat (14): (1) DB'ni ko'rgan har kim (dasturchi, admin, hacker DB'ni o'g'irlasa) — barcha parollarni biladi. (2) Odamlar bir parolni ko'p joyda ishlatadi — bitta sizish, boshqa hisoblar ham xavf ostida. (3) Bu — qonun buzilishi (ma'lumot himoyasi). Parol hech qachon ochiq saqlanmaydi — bu eng asosiy qoida.

2.3. Hashing — yechim (bir tomonlama funksiya)

Hashing — ma'lumotni qaytmas (bir tomonlama) tarzda boshqa qiymatga aylantirish (5.3: crypto'da ko'rdik). Asl parolni saqlamaymiz — uning hashini saqlaymiz:

text
  "parol123"  ──hash──▶  "$2b$12$KIXxPf... (60 belgi)"
  Hash'dan ortga "parol123"ni TIKLAB BO'LMAYDI (bir tomonlama)

  Login'da: kiritilgan parolni HASH qilamiz, saqlangan hash bilan SOLISHTIRAMIZ.
  Bir xil bo'lsa — parol to'g'ri (asl parolni hech qachon bilmaymiz!)

Hashing vs shifrlash (encryption) farqi: shifrlash — ikki tomonlama (kalit bilan ortga ochiladi); parol uchun yaroqsiz (kalit sizsa — hammasi ochiladi). Hashing — bir tomonlama (ortga yo'q); parol uchun aynan shu kerak. Parolni hech qachon "ochib" ko'rmaymiz — faqat solishtramiz.

2.4. Oddiy hash yetarli emas — salt va rainbow table

Oddiy hash (MD5, SHA-256 — 5.3) parol uchun yetarli emas. Ikki sabab:

1. Bir xil parol bir xil hash: ikki odam "parol123" ishlatsa, hash bir xil. Hacker buni ko'radi.

2. Rainbow table (kamalak jadvali): hacker oldindan millionlab parol va ularning hash'ini hisoblab qo'ygan jadval. Hash'ni jadvaldan qidirib, parolni topadi (tez).

Yechim — salt (tuz): har parolga tasodifiy qiymat (salt) qo'shib, keyin hash qilish:

text
  "parol123" + salt "xY9$"  hash A
  "parol123" + salt "qW2#"  hash B   (bir xil parol, LEKIN har xil hash!)

   Rainbow table ishlamaydi (har parol noyob salt bilan)
   Bir xil parollar — har xil hash (kim nima ishlatganini bilib bo'lmaydi)

2.5. bcrypt — parol uchun maxsus (sekin — bu yaxshi)

bcrypt — parol hashlash uchun maxsus yaratilgan algoritm (authgear/owasp). Ikki muhim xususiyat:

  1. Salt'ni avtomatik ichiga oladi (hash'ning bir qismi sifatida — qo'lda boshqarmaysiz).
  2. Ataylab SEKIN — va sozlanadigan tezlik ("cost factor"). Bu — xavfsizlik xususiyati:
text
  Oddiy hash (SHA): juda tez  hacker soniyasiga MILLIONLAB parol sinaydi (brute-force)
  bcrypt: ataylab sekin  hacker soniyasiga juda KAM sinaydi (brute-force amalda imkonsiz)

  Login bir marta bo'ladi (sekinlik sezilmaydi); hacker MILLIARD marta sinaydi (sekinlik to'sadi)

Cost factor (rounds): bcrypt'ning sekinligi — 10, 12 kabi son bilan sozlanadi. Har +1 — ikki barobar sekinroq. 10–12 — 2026'da tavsiya (owasp). Kompyuterlar tezlashgani sayin, cost oshiriladi. argon2 — zamonaviy muqobil (yaxshiroq nazariy xususiyat); bcrypt — keng qo'llab-quvvatlangan, sinovdan o'tgan. Ikkalasi ham to'g'ri tanlov.

2.6. bcrypt hash tuzilishi (under the hood)

bcrypt hash — bitta satr, hamma narsa ichida:

text
  $2b$12$KIXxPfH6.../EeF1qz...
   │  │  │           │
   │  │  │           └── hash (parol + salt natijasi)
   │  │  └── salt (22 belgi — avtomatik)
   │  └── cost factor (12 — rounds)
   └── algoritm versiyasi ($2b$)

   salt hash ichida! Solishtirishda bcrypt o'zi ajratib oladi.
   Shuning uchun "salt'ni alohida saqlash" kerak emas (bcrypt o'zi qiladi).

Nega bcrypt.compare (=== emas) — timing (14): parolni solishtirganda hash === saqlangan ishlatib bo'lmaydi. Oddiy === belgilarni ketma-ket taqqoslaydi va birinchi farqda darhol to'xtaydi — natijada to'g'ri javob biroz uzoqroq ishlaydi. Hacker bu vaqt farqini (mikrosoniya) o'lchab, belgi-belgi parolni taxmin qilishi mumkin (timing attack). bcrypt.comparedoimiy vaqtli (constant-time): natija nima bo'lishidan qat'i nazar bir xil vaqt ishlaydi, vaqt sizib chiqmaydi. Shuning uchun parol/token solishtirishda doim maxsus funksiya (bcrypt.compare, crypto.timingSafeEqual — 5.3) ishlating.

2.7. Ro'yxatdan o'tish va login oqimi (umumiy rasm)

text
  RO'YXATDAN O'TISH (register):
  Foydalanuvchi: email + parol 
    1. validatsiya 5.9-bob — email to'g'ri, parol kuchli
    2. email band emasligini tekshiring (DB — 6)
    3. parolni HASH qiling (bcrypt — 2.5)
    4. DB'ga saqlang (hash, ochiq parol EMAS)

  LOGIN (kirish):
  Foydalanuvchi: email + parol 
    1. email bo'yicha foydalanuvchini toping (DB)
    2. kiritilgan parolni saqlangan hash bilan SOLISHTIRING (bcrypt.compare)
    3. to'g'ri  "kirgan" holatini bering (session/JWT — 2.8+)
    4. noto'g'ri  401 (xato — 5.7)

2.8. HTTP holatsiz — "kirgan holda qolish" muammosi

Asosiy muammo: HTTP holatsiz (stateless — 0.4). Har so'rov mustaqil — server oldingi so'rovni "eslamaydi". Login qildingiz, lekin keyingi so'rovda server sizni tanimaydi (yangi so'rov, yangi ulanish):

text
  So'rov 1: login (email+parol)  server: "to'g'ri, kirdingiz" 
  So'rov 2: profilni ber  server: "siz kimsiz? (oldingisini eslamayman)" 

   Har so'rovda qayta parol so'rash MUMKIN EMAS (dahshatli UX)
   Yechim: har so'rovga "men kirdim" ISBOTINI biriktirish

Ikki asosiy yechim: session (stateful — server eslaydi) va token/JWT (stateless — isbot o'zida).

2.9. Session-based auth (stateful — server eslaydi)

Session yondashuvi: login muvaffaqiyatli bo'lsa, server xotirasida/DB'da "sessiya" yozadi (kim, qachon) va unga noyob session ID beradi. Bu ID foydalanuvchiga cookie 2.10-bob orqali beriladi. Keyingi so'rovlarda cookie avtomatik keladi, server ID bo'yicha sessiyani topadi (loginradius):

text
  Login  server sessiya yaratadi:
    session store: { "abc123": { userId: 7, ... } }    server ESLAYDI (stateful)
    foydalanuvchiga cookie: sessionId=abc123

  Har so'rov  cookie (sessionId=abc123) avtomatik keladi 
    server store'dan "abc123"ni topadi  "ha, bu user 7" 

"Stateful" — server holatni saqlaydi (kim kirgan). Session store — xotira (kichik), yoki Redis 5.21-bob / DB (ko'p server uchun).

Cookie — server brauzerga beradigan kichik ma'lumot; brauzer uni saqlaydi va har so'rovda avtomatik qaytaradi 0.4-bob:

text
  Server javobida:  Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax
  Brauzer saqlaydi va har so'rovda:  Cookie: sessionId=abc123  (avtomatik!)

Cookie'ning kuchiavtomatik yuboriladi (qo'lda boshqarmaysiz); brauzer domenga bog'lab saqlaydi. Session ID, token shu yerda saqlanadi. Lekin xavfsiz sozlanishi shart 2.11-bob.

Cookie'ning xavfsizlik atributlari — eng muhim (MDN, 14):

text
  HttpOnly  — JavaScript cookie'ni O'QIY OLMAYDI (document.cookie)
               XSS hujumi cookie'ni o'g'irlay olmaydi (14)

  Secure    — faqat HTTPS orqali yuboriladi (shifrlangan — 14)
               tarmoqda ochiq ketmaydi

  SameSite  — cookie qaysi so'rovlarda yuboriladi:
              Strict — faqat o'z saytdan; Lax — asosan o'z saytdan (default, tavsiya);
              None — har joydan (Secure bilan birga)
               CSRF hujumiga qarshi himoya (14)

Eng muhim qoida (14): auth cookie'si HttpOnly bo'lsin (JS o'qiy olmasin — XSS himoyasi), Secure (production — HTTPS), SameSite=Lax (CSRF himoyasi). Bu uchtasi — 2026 standart himoyasi. Token'ni localStorageda saqlash — XSS uchun ochiq 2.15-bob.

CSRF nima (14): Cross-Site Request Forgery — boshqa sayt sizning nomingizdan so'rov yuborishi. Cookie avtomatik yuborilgani uchun 2.10-bob xavf shu yerda: agar foydalanuvchi bank.uz'da kirgan bo'lsa va aldab boshqa (yomon.uz) saytga kiritilsa, o'sha sahifa yashirin bank.uz'ga so'rov yuborsa — brauzer cookie'ni avtomatik biriktiradi, server "haqiqiy foydalanuvchi" deb qabul qiladi. SameSite=Lax aynan shuni to'sadi: cookie faqat o'z saytdan kelgan so'rovlarga qo'shiladi. Diqqat: CSRF — cookie-auth muammosi; Authorization header'da token yuborilsa (qo'lda qo'shiladi, avtomatik emas), CSRF tabiiy yo'q.

2.12. Token-based auth (stateless — isbot o'zida)

Ikkinchi yondashuv — token (asosan JWT). Login'da server imzolangan token beradi; bu token o'zida foydalanuvchi ma'lumotini va imzo'ni saqlaydi. Server hech narsa eslamaydi — har so'rovda tokenni tekshiradi (stateless — descope):

text
  Login  server JWT yasaydi (imzolangan): "user 7, admin, muddati 1soat"
     foydalanuvchiga token beradi
     server HECH NARSA SAQLAMAYDI (stateless)

  Har so'rov  token keladi (header/cookie) 
    server imzoni TEKSHIRADI  ishonchli bo'lsa "bu user 7" 
    (store'ga qaramaydi — token o'zi yetarli)

"Stateless" — server holat saqlamaydi; token o'zi ishonchli isbot (imzo bilan). Ko'p serverga oson masshtablanadi (session store kerak emas — har server tokenni o'zi tekshiradi).

2.13. JWT tuzilishi — header.payload.signature (under the hood)

JWT (JSON Web Token) — uch qismdan iborat, nuqta bilan ajratilgan satr (jwt.io):

text
  xxxxx.yyyyy.zzzzz
  │     │     │
  │     │     └── SIGNATURE (imzo) — maxfiy kalit bilan; o'zgartirishni aniqlaydi
  │     └── PAYLOAD — ma'lumot (userId, rol, muddat) — base64 (OCHIQ o'qiladi!)
  └── HEADER — algoritm va tur (base64)

  Misol:
  eyJhbGc...  (header: {"alg":"HS256","typ":"JWT"})
  eyJzdWI...  (payload: {"sub":7,"rol":"admin","exp":1700000000})
  SflKxwRJ... (signature)

Muhim (14): payload shifrlangan EMAS — faqat base64 (har kim o'qiydi!). Maxfiy ma'lumot (parol, karta) JWT'ga solmang. JWT'ning kuchi — maxfiylikda emas, yaxlitlikda: imzo o'zgartirishni aniqlaydi (kimdir payload'ni o'zgartirsa — imzo mos kelmaydi, rad etiladi).

2.14. JWT imzolash va tekshirish (HS256 vs RS256)

Imzo maxfiy kalit bilan yaratiladi va tekshiriladi:

text
  HS256 (simmetrik) — bitta MAXFIY kalit imzolaydi VA tekshiradi
     sodda; bir server/monolit uchun (kalit hammada bo'lishi kerak)

  RS256 (asimmetrik) — PRIVATE kalit imzolaydi, PUBLIC kalit tekshiradi
     mikroservis uchun xavfsizroq (auth servis imzolaydi, boshqalar
      faqat public bilan tekshiradi — private faqat bitta joyda — 14)

Tanlov: monolit (bitta backend) — HS256 (sodda, JWT_SECRET — 5.8). Mikroservis 9.9-bob yoki tashqi tekshirish — RS256 (auth.com/workos tavsiyasi). JWT_SECRET kuchli bo'lsin (kamida 32 belgi — crypto.randomBytes — 5.3, 5.8).

Frontend (11) tokenni qayerda saqlaydi?

text
  localStorage    — JS o'qiydi  XSS hujumi o'g'irlaydi (14)  (xavfli)
  httpOnly cookie — JS o'qiy olmaydi  XSS himoyasi (14)  (tavsiya)

2026 tavsiya (14): tokenni httpOnly cookie'da saqlang (localStorageda emas — workos/authgear). localStorage — har qanday XSS skripti o'qiydi (token o'g'irlanadi). httpOnly cookie — JS ko'rmaydi. Cookie ishlatsangiz — CSRF himoyasi ham kerak (SameSite — 2.11).

2.16. Session vs JWT — taqqoslash (qachon qaysi)

Xususiyat Session (stateful) JWT (stateless)
Server saqlaydi Ha (store/Redis) Yo'q
Masshtab (ko'p server) Shared store kerak Oson (kalit yetarli)
Bekor qilish (logout) Oson (store'dan o'chir) Qiyin (muddat tugaguncha amal qiladi)
Ma'lumot Server'da Token ichida
Mikroservis Murakkabroq Mos

Tavsiya (clerk/auth0): ko'p ilova uchun — qisqa muddatli JWT (access — 15 daqiqa) + refresh token (uzoq — 5.16) gibrid yondashuvi: JWT tezligi + bekor qilish imkoni. Klassik server-rendered ilova — session. Ikkalasi ham to'g'ri; loyihaga qarab.

2.17. JWT muammosi — bekor qilish va muddat (5.16 ko'prik)

JWT'ning kamchiligi: bekor qilib bo'lmaydi (stateless — server eslamaydi). Token o'g'irlansa yoki foydalanuvchi logout qilsa ham, token muddati tugaguncha amal qiladi:

text
  Yechim:
  1. Qisqa muddat (access token 15 daqiqa — exp)  o'g'irlansa, tez tugaydi
  2. Refresh token (uzoq, DB'da — bekor qilinadi)  yangi access olish 5.16-bob
  3. Blacklist (Redis — bekor qilingan tokenlar — 5.21)

Bu — 5.16-bob (Access/Refresh tokens) mavzusi. Hozir asosni (bitta JWT) tushunamiz; keyingi bobda professional token strategiyasini quramiz.

2.18. Xavfsizlik amaliyotlari (14)

text
   Parolni bcrypt/argon2 bilan hash (cost 10-12 — 2.5); ochiq saqlamang 2.2-bob
   Login'da xato xabari UMUMIY ("email yoki parol noto'g'ri" — qaysi xato ekanin oshkor qilmang — 14)
   JWT_SECRET kuchli (32+ belgi, .env — 5.8); RS256 mikroservisda 2.14-bob
   Token httpOnly cookie'da (localStorage emas — 2.15)
   Cookie: HttpOnly + Secure + SameSite 2.11-bob
   Login'ga rate limiting (brute-force — 5.20, 14)
   Parol kuchliligini talab qiling (validatsiya — 5.9)
   Qisqa access token + refresh 5.16-bob; HTTPS (14)
   Maxfiy ma'lumotni JWT payload'ga solmang (2.13)

3. Sintaksis — tez ma'lumotnoma

js
// bcrypt (2.5)
import bcrypt from "bcrypt";
const hash = await bcrypt.hash(parol, 12);          // hashlash (cost 12)
const togri = await bcrypt.compare(parol, hash);    // solishtirish  true/false

// JWT (2.13)
import jwt from "jsonwebtoken";
const token = jwt.sign({ sub: user.id, rol }, SECRET, { expiresIn: "1h" });   // yasash
const payload = jwt.verify(token, SECRET);          // tekshirish (xato  throw)

// Cookie 2.11-bob — Express
res.cookie("token", token, { httpOnly: true, secure: true, sameSite: "lax", maxAge: 3600000 });
res.clearCookie("token");                            // logout

// Session 2.9-bob — express-session
app.use(session({ secret, resave: false, saveUninitialized: false, store }));
req.session.userId = user.id;                        // saqlash

4. Batafsil kod namunalari

Misol 1 — bcrypt: hash va solishtirish (2.5)

js
import bcrypt from "bcrypt";                          // npm install bcrypt

// HASHLASH (ro'yxatdan o'tishda — 2.7)
const parol = "MaxfiyParol123";
const hash = await bcrypt.hash(parol, 12);            // cost 12 (2.5)
console.log(hash);   // "$2b$12$KIXx..." (60 belgi — salt ichida, 2.6)

// SOLISHTIRISH (login'da — 2.7)
const togri = await bcrypt.compare("MaxfiyParol123", hash);   // true
const xato = await bcrypt.compare("BoshqaParol", hash);       // false
// bcrypt.compare hash'dan salt'ni o'zi ajratadi 2.6-bob — qo'lda salt kerak emas

Misol 2 — Ro'yxatdan o'tish (register — 2.7)

js
import bcrypt from "bcrypt";
import { z } from "zod";                              // validatsiya (5.9)

const signupSchema = z.object({
  email: z.string().trim().toLowerCase().email(),
  parol: z.string().min(8).regex(/[A-Z]/, "Katta harf").regex(/\d/, "Raqam"),   // (5.9)
}).strict();

export const signup = async (req, res, next) => {
  try {
    // 1. Validatsiya (5.9)
    const { email, parol } = signupSchema.parse(req.body);
    // 2. Email band emasligini tekshiring (DB — 6)
    const mavjud = await User.findOne({ email });
    if (mavjud) return res.status(409).json({ error: "Email band" });   // 409 (5.7)
    // 3. Parolni HASH qiling 2.5-bob — ochiq saqlamang!
    const parolHash = await bcrypt.hash(parol, 12);
    // 4. DB'ga saqla (hash, ochiq parol EMAS — 2.2)
    const user = await User.create({ email, parol: parolHash });
    // 5. Javob (parolni QAYTARMA!)
    res.status(201).json({ id: user.id, email: user.email });
  } catch (err) {
    next(err);                                         // (5.10)
  }
};

Misol 3 — Login (kirish + JWT — 2.7, 2.13)

js
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { config } from "../config/index.js";          // (5.8)

export const login = async (req, res, next) => {
  try {
    const { email, parol } = req.body;                 // (validatsiya — 5.9)
    // 1. Foydalanuvchini top (DB — 6)
    const user = await User.findOne({ email: email.toLowerCase() });
    // 2. UMUMIY xato (email yo'q/parol xato — qaysiligini oshkor qilmang! — 2.18, 14)
    if (!user) return res.status(401).json({ error: "Email yoki parol noto'g'ri" });
    // 3. Parolni solishtir (2.5)
    const togri = await bcrypt.compare(parol, user.parol);
    if (!togri) return res.status(401).json({ error: "Email yoki parol noto'g'ri" });
    // 4. JWT yasash 2.13-bob — imzolangan
    const token = jwt.sign(
      { sub: user.id, rol: user.rol },                 // payload (maxfiy emas — 2.13)
      config.jwt.secret,                               // maxfiy kalit (5.8)
      { expiresIn: "1h" }                              // muddat (2.17)
    );
    // 5. Token'ni httpOnly cookie'da ber (2.15)
    res.cookie("token", token, {
      httpOnly: true,                                  // JS o'qiy olmaydi (2.11, 14)
      secure: config.isProd,                           // production HTTPS (2.11)
      sameSite: "lax",                                 // CSRF himoyasi (2.11)
      maxAge: 60 * 60 * 1000,                          // 1 soat
    });
    res.json({ user: { id: user.id, email: user.email, rol: user.rol } });
  } catch (err) {
    next(err);
  }
};

Misol 4 — Auth middleware (himoyalangan route — 2.12)

js
import jwt from "jsonwebtoken";
import { config } from "../config/index.js";

// Tokenni tekshiruvchi middleware (5.6)
export const auth = (req, res, next) => {
  // Token'ni cookie'dan yoki Authorization header'dan ol (2.15)
  const token = req.cookies?.token ||
    (req.headers.authorization?.startsWith("Bearer ") &&
     req.headers.authorization.slice(7));

  if (!token) return res.status(401).json({ error: "Avtorizatsiya kerak" });   // (5.7)

  try {
    const payload = jwt.verify(token, config.jwt.secret);   // tekshir (2.14)
    req.user = { id: payload.sub, rol: payload.rol };       // so'rovga biriktir
    next();                                                  // ruxsat (5.6)
  } catch {
    return res.status(401).json({ error: "Token noto'g'ri yoki muddati tugagan" });
  }
};

// Ishlatish — himoyalangan route (5.6)
app.get("/api/profile", auth, (req, res) => {
  res.json({ userId: req.user.id });   // req.user — auth'dan (ishonchli)
});
js
import cookieParser from "cookie-parser";             // npm install cookie-parser
app.use(cookieParser());                              // cookie'larni req.cookies'ga (5.6)

app.get("/api/me", (req, res) => {
  const token = req.cookies.token;                    // cookie'dan o'qish (2.10)
  if (!token) return res.status(401).json({ error: "Kirilmagan" });
  // ... tekshirish (Misol 4) ...
});
js
export const logout = (req, res) => {
  // Cookie'ni o'chir (sozlamalar mos bo'lishi kerak — 2.11)
  res.clearCookie("token", {
    httpOnly: true,
    secure: config.isProd,
    sameSite: "lax",
  });
  res.json({ message: "Tizimdan chiqdingiz" });
};
//  JWT stateless — logout faqat cookie'ni o'chiradi; token muddatigacha amal qiladi (2.17)
//    To'liq bekor qilish uchun — refresh token + blacklist (5.16)

Misol 7 — JWT'ni qo'lda tekshirish (under the hood — 2.13)

js
import jwt from "jsonwebtoken";

const token = jwt.sign({ sub: 7, rol: "admin" }, "secret", { expiresIn: "1h" });

// Payload — IMZOSIZ ham o'qiladi (base64 — 2.13)! Maxfiy emas
const ochiq = jwt.decode(token);                      // tekshirmasdan o'qish
console.log(ochiq);   // { sub: 7, rol: "admin", iat: ..., exp: ... }

// Imzo bilan TEKSHIRISH (xavfsiz — 2.14)
try {
  const togri = jwt.verify(token, "secret");          // imzo to'g'rimi?
  console.log("Ishonchli:", togri.sub);
} catch (err) {
  console.log("Soxta/muddati tugagan:", err.message); // TokenExpiredError / JsonWebTokenError
}
// jwt.decode — faqat o'qish (tekshirmaydi!); jwt.verify — IMZONI tekshiradi (2.13)

Misol 8 — Session-based auth (express-session — 2.9)

js
import session from "express-session";                // npm install express-session
import { RedisStore } from "connect-redis";           // production store (5.21)

app.use(session({
  store: new RedisStore({ client: redisClient }),     // Redis store (ko'p server — 2.9)
  secret: config.session.secret,                      // cookie imzosi (5.8)
  resave: false,
  saveUninitialized: false,
  cookie: { httpOnly: true, secure: config.isProd, sameSite: "lax", maxAge: 86400000 },   // (2.11)
}));

// Login — sessiyaga yoz (2.9)
app.post("/login", async (req, res) => {
  const user = await User.findOne({ email: req.body.email });
  if (!user || !(await bcrypt.compare(req.body.parol, user.parol))) {
    return res.status(401).json({ error: "Email yoki parol noto'g'ri" });   // (2.18)
  }
  req.session.userId = user.id;                        // server ESLAYDI (stateful — 2.9)
  res.json({ message: "Kirdingiz" });
});

// Himoyalangan — sessiyadan o'qi
app.get("/profile", (req, res) => {
  if (!req.session.userId) return res.status(401).json({ error: "Kirilmagan" });
  res.json({ userId: req.session.userId });
});

// Logout — sessiyani o'chir (oson bekor qilish — 2.16)
app.post("/logout", (req, res) => {
  req.session.destroy(() => res.json({ message: "Chiqdingiz" }));
});

Misol 9 — User modelida parol himoyasi (DB — 6)

js
// Mongoose model (6) — parolni hech qachon JSON'da qaytarmang
const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  parol: { type: String, required: true, select: false },   // default'da QAYTARILMAYDI (14)
  rol: { type: String, enum: ["user", "admin"], default: "user" },
});

// Saqlashdan oldin avtomatik hash (pre-save hook — 6)
userSchema.pre("save", async function (next) {
  if (!this.isModified("parol")) return next();       // faqat o'zgarganda
  this.parol = await bcrypt.hash(this.parol, 12);     // (2.5)
  next();
});

// Solishtirish metodi
userSchema.methods.parolToOgri = function (parol) {
  return bcrypt.compare(parol, this.parol);           // (2.5)
};

Misol 10 — To'liq auth router (2.7)

js
import { Router } from "express";
import { auth } from "../middleware/auth.js";

const router = Router();
router.post("/signup", signup);                       // ro'yxat (Misol 2)
router.post("/login", login);                         // login (Misol 3)
router.post("/logout", logout);                       // logout (Misol 6)
router.get("/me", auth, async (req, res) => {         // himoyalangan (Misol 4)
  const user = await User.findById(req.user.id);      // (6)
  res.json({ id: user.id, email: user.email, rol: user.rol });
});
export default router;

Misol 11 — Parol o'zgartirish (xavfsiz — 2.5)

js
export const parolOzgartir = async (req, res, next) => {
  try {
    const { eskiParol, yangiParol } = req.body;        // (validatsiya — 5.9)
    const user = await User.findById(req.user.id).select("+parol");   // parol bilan (Misol 9)
    // 1. Eski parolni tasdiqla (2.5)
    if (!(await bcrypt.compare(eskiParol, user.parol))) {
      return res.status(401).json({ error: "Eski parol noto'g'ri" });
    }
    // 2. Yangi parolni hash qiling va saqlang
    user.parol = await bcrypt.hash(yangiParol, 12);    // (2.5)
    await user.save();
    res.json({ message: "Parol o'zgartirildi" });
    //  Production: barcha eski tokenlarni bekor qiling (5.16)
  } catch (err) { next(err); }
};

Misol 12 — Login'ga rate limiting (brute-force himoyasi — 2.18, 14)

js
import rateLimit from "express-rate-limit";           // (5.20)

// Login endpoint'iga maxsus chegara (brute-force'ga qarshi — 14)
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,                            // 15 daqiqa
  max: 5,                                              // 5 urinish
  message: { error: "Juda ko'p urinish. 15 daqiqadan keyin urinib ko'ring" },
  standardHeaders: true,
});

app.post("/api/auth/login", loginLimiter, login);     // (Misol 3)
//  hacker parolni cheksiz sinab ko'rolmaydi (5.20'da chuqur)

5. To'g'ri va noto'g'ri holatlar

1) Parolni ochiq saqlash

js
//  FALOKAT — ochiq parol (14, 2.2)
await User.create({ email, parol });

//  hash (bcrypt)
await User.create({ email, parol: await bcrypt.hash(parol, 12) });

2) Login xatosida qaysi maydon xato ekanini aytish

js
//  hacker'ga ma'lumot ("email yo'q"  email mavjudligini bilib oladi — 14, 2.18)
if (!user) return res.json({ error: "Bunday email yo'q" });

//  umumiy xabar
return res.status(401).json({ error: "Email yoki parol noto'g'ri" });

3) Tokenni localStorage'da saqlash

text
 localStorage  XSS o'g'irlaydi (14, 2.15)
 httpOnly cookie

4) Maxfiy ma'lumotni JWT payload'ga solish

js
//  payload base64 — har kim o'qiydi (14, 2.13)
jwt.sign({ parol: user.parol, karta: "..." }, secret);

//  faqat ID/rol (maxfiy emas)
jwt.sign({ sub: user.id, rol: user.rol }, secret);

5) Zaif JWT_SECRET

js
//  oson topiladigan kalit (14)
jwt.sign(payload, "secret123");

//  kuchli, .env'da (crypto.randomBytes — 5.3, 5.8)
jwt.sign(payload, config.jwt.secret);   // 32+ belgi

6) Cookie'ni xavfsizlik flag'larisiz

js
//  HttpOnly/Secure/SameSite yo'q (14, 2.11)
res.cookie("token", token);

//  to'liq himoya
res.cookie("token", token, { httpOnly: true, secure: true, sameSite: "lax" });

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — bcrypt.compare doim false

Sababi: hash saqlanmagan (ustun qisqa — DB), yoki ikki marta hash qilingan (pre-save hook + qo'lda — Misol 9). Yechimi: ustun uzunligini tekshiring (60+); bir marta hash.

Xato 2 — JsonWebTokenError: invalid signature

Sababi: verify'dagi kalit sign'dagidan farq qiladi 5.8-bob. Yechimi: bir xil JWT_SECRET; .env to'g'ri yuklanganmi.

Xato 3 — TokenExpiredError

Sababi: token muddati tugagan (expiresIn — 2.17). Yechimi: refresh token bilan yangilash 5.16-bob; qayta login.

Xato 4 — Cookie kelmaydi (frontend so'rovida)

Sababi: CORS credentials yo'q, yoki frontend fetch'da credentials: "include" yo'q 5.20-bob. Yechimi: server cors({ credentials: true, origin }); frontend credentials: "include".

Xato 5 — req.cookies undefined

Sababi: cookie-parser middleware yo'q (Misol 5). Yechimi: app.use(cookieParser()).

Xato 6 — Parol javobda qaytyapti

Sababi: model parolni qaytaradi (Misol 9). Yechimi: select: false; javobda parolni olib tashlang.

Xato 7 — SameSite cookie cross-site kelmaydi

Sababi: frontend va backend turli domen; SameSite=Lax/Strict bloklaydi. Yechimi: SameSite=None + Secure (HTTPS); yoki bir domen (proxy).


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • crypto 5.3-bob: hashing asosi; JWT_SECRET yaratish.
  • Express 5.6-bob: auth middleware, route himoyasi.
  • Env 5.8-bob: JWT_SECRET, session secret.
  • Validatsiya 5.9-bob: email/parol; parol kuchliligi.
  • Error handling 5.10-bob: 401/409; umumiy xato.
  • Refresh tokens 5.16-bob: bu asosni davom ettiradi.
  • RBAC 5.17-bob: auth'dan keyin avtorizatsiya (req.user.rol).
  • OTP/SMS 5.18-bob: ikki bosqichli; telefon auth.
  • Rate limiting 5.20-bob: login brute-force himoyasi.
  • Redis 5.21-bob: session store; token blacklist.
  • DB (6): user model, parol hash.
  • Frontend (11): login forma, cookie/token, himoyalangan route.
  • NestJS 8.16-bob: Passport, JWT strategy, guards — shu g'oya.

8. Eng yaxshi amaliyotlar (best practices)

  • Parolni bcrypt/argon2 bilan hash (cost 10-12 — 2.5); ochiq saqlamang 2.2-bob.
  • Login xatosi UMUMIY ("email yoki parol noto'g'ri" — 2.18, 14).
  • Token httpOnly cookie'da (localStorage emas — 2.15, 14).
  • Cookie: HttpOnly + Secure + SameSite=Lax (2.11, 14).
  • JWT_SECRET kuchli (32+ belgi, .env — 5.8); RS256 mikroservisda 2.14-bob.
  • Maxfiy ma'lumotni JWT payload'ga solmang (base64 ochiq — 2.13).
  • Qisqa access token (15min-1soat) + refresh (5.16, 2.17).
  • Login'ga rate limiting (brute-force — 5.20, Misol 12).
  • Parol kuchliligini talab qiling (validatsiya — 5.9).
  • Parolni javobda qaytarmang (select: false — Misol 9).
  • HTTPS (production — 14); logout (cookie tozalash + refresh bekor — 5.16).
  • Session uchun Redis store (ko'p server — 2.9, 5.21).

9. Amaliy loyiha: "To'liq Autentifikatsiya Tizimi"

Autentifikatsiyani professional, xavfsiz darajada mustahkamlash.

Maqsad

bcrypt, JWT va xavfsiz cookie bilan to'liq auth tizimini qurish: ro'yxatdan o'tish, login, himoyalangan route'lar, logout va parol o'zgartirish — xavfsizlik amaliyotlari bilan.

Talablar (requirements)

  1. Ro'yxatdan o'tish: validatsiya (email, kuchli parol — 5.9); email band tekshiruvi; bcrypt hash; parolni qaytarmaslik (Misol 2, 2.7).
  2. Login: parolni bcrypt.compare; UMUMIY xato xabari; JWT yasash; httpOnly cookie (Misol 3, 2.18).
  3. Auth middleware: token tekshiring (cookie/header); req.user; himoyalangan route'lar (Misol 4, 2.12).
  4. Logout: cookie tozalash (Misol 6, 2.11).
  5. Profil (/me): himoyalangan; joriy foydalanuvchi ma'lumoti (Misol 10).
  6. Parol o'zgartirish: eski parolni tasdiqlash; yangi hash (Misol 11).
  7. Cookie xavfsizligi: HttpOnly + Secure + SameSite (2.11, 14).
  8. JWT_SECRET: .env'da, kuchli (crypto.randomBytes — 5.3, 5.8).
  9. Rate limiting: login endpoint'iga (brute-force — Misol 12, 5.20).
  10. User model: parol select: false; pre-save hash (Misol 9, 6).
  11. (Bonus) Session varianti: express-session + Redis bilan ham yozib, JWT bilan solishtir (Misol 8, 2.16).

Maslahatlar (hint)

  • bcrypt.hash(parol, 12) / bcrypt.compare 2.5-bob.
  • Login xato: qaysi maydon xato ekanini aytmang (2.18, 2-xato).
  • Cookie: { httpOnly: true, secure: isProd, sameSite: "lax" } 2.11-bob.
  • cookie-parser middleware (5-xato); CORS credentials (4-xato).
  • JWT: jwt.sign({ sub, rol }, secret, { expiresIn }) 2.13-bob; maxfiy ma'lumot solmang.
  • Parolni javobdan olib tashlang (select: false — 6-xato).

"Tayyor" mezonlari (acceptance criteria)

  • Ro'yxatdan o'tish: parol hash bo'lib saqlanadi (ochiq emas).
  • Login: to'g'ri parolda JWT cookie beriladi; xato umumiy.
  • Auth middleware himoyalangan route'larni qo'riqlaydi.
  • Logout cookie'ni tozalaydi.
  • /me joriy foydalanuvchini qaytaradi.
  • Parol o'zgartirish eski parolni tasdiqlaydi.
  • Cookie HttpOnly+Secure+SameSite bilan.
  • JWT_SECRET .env'da, kuchli.
  • Login rate limiting bilan himoyalangan.
  • Parol hech qachon javobda qaytmaydi.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda backend'ning yuragini — autentifikatsiyani — chuqur o'rgandik:

  • Authentication (kim siz?) vs authorization (nimaga haqlisiz? — 5.17 — 2.1).
  • Parolni hech qachon ochiq saqlamang 2.2-bobhashing (bir tomonlama — 2.3); salt (rainbow table — 2.4); bcrypt (sekin, cost 10-12, salt ichida — 2.5, 2.6).
  • HTTP holatsiz (2.8) session (stateful, server eslaydi — 2.9) yoki JWT (stateless, isbot o'zida — 2.12).
  • Cookie (avtomatik — 2.10); xavfsizlik (HttpOnly+Secure+SameSite — 2.11, 14).
  • JWT tuzilishi (header.payload.signature; payload OCHIQ — 2.13); imzo (HS256/RS256 — 2.14); httpOnly cookie'da sakla 2.15-bob.
  • Session vs JWT 2.16-bob; JWT bekor qilish muammosi refresh token 2.17-bob; xavfsizlik amaliyotlari (2.18, 14).

Keyingi bob — 5.16-bob: Access/Refresh tokens, token strategiyasi. Bitta JWT'ning kamchiligini (bekor qilib bo'lmaydi, muddat muammosi — 2.17) ko'rdik. Endi professional token strategiyasini quramiz: qisqa muddatli access token + uzoq muddatli refresh token, token rotatsiyasi, xavfsiz saqlash va bekor qilish. Bu — zamonaviy auth'ning standart yechimi.


Foydalanilgan rasmiy/ishonchli manbalar

  • WorkOS / Authgear — Node.js authentication best practices 2026 (bcrypt, JWT, cookie)
  • MDN — Secure cookie configuration (HttpOnly, Secure, SameSite); jwt.io — JWT struktura
  • LoginRadius / Descope — Session vs Token (stateful vs stateless); OWASP — parol hashlash

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
5.15-bob: Autentifikatsiya — session, cookie, JWT, bcrypt — Wisar