Web xavfsizligi va autentifikatsiya

Autentifikatsiya va avtorizatsiya

Deyarli har bir ilova foydalanuvchini taniydi (autentifikatsiya) va unga nima qilishga ruxsat berilganini hal qiladi (avtorizatsiya). Ushbu darsda biz bu ikki tushunchani ajratamiz, parollarni xavfsiz saqlashni (hashlash va salt), hamda foydalanuvchi holatini saqlashning ikki asosiy usulini โ€” sessiya va tokenlarni โ€” ko'rib chiqamiz.

Autentifikatsiya va avtorizatsiya farqi

Bu ikki so'z ko'pincha aralashtiriladi, lekin ular butunlay boshqa narsani anglatadi:

Oddiy misol: mehmonxonaga kirganingizda pasportingizni ko'rsatasiz โ€” bu autentifikatsiya. Sizga berilgan kalit faqat o'z xonangizni ochadi, boshqa xonalarni emas โ€” bu avtorizatsiya.

// Autentifikatsiya: foydalanuvchi kimligini aniqlaymiz
function authMiddleware(req, res, next) {
  const foydalanuvchi = tokendanFoydalanuvchiOl(req);
  if (!foydalanuvchi) {
    return res.status(401).json({ xato: 'Tizimga kiring' }); // 401 = kim ekaning noma'lum
  }
  req.foydalanuvchi = foydalanuvchi;
  next();
}

// Avtorizatsiya: bu foydalanuvchiga ruxsat bormi?
function faqatAdmin(req, res, next) {
  if (req.foydalanuvchi.rol !== 'admin') {
    return res.status(403).json({ xato: 'Ruxsat yo'q' }); // 403 = kimligi ma'lum, lekin ruxsat yo'q
  }
  next();
}
HTTP status kodlarini eslab qoling: 401 Unauthorized โ€” "kimligingiz noma'lum, tizimga kiring" (aslida autentifikatsiya haqida); 403 Forbidden โ€” "kimligingiz ma'lum, lekin bu amalga ruxsatingiz yo'q" (avtorizatsiya haqida).

Parolni HECH QACHON ochiq saqlamang

Bu โ€” autentifikatsiyadagi eng muhim qoida. Parolni ma'lumotlar bazasida o'qish mumkin bo'lgan holatda saqlash โ€” jiddiy xato. Agar bazangiz o'g'irlansa (bu tez-tez sodir bo'ladi), barcha foydalanuvchilar parollari ochiladi. Bundan ham yomoni: odamlar ko'pincha bir parolni bir necha saytda ishlatadi.

Parolni hech qachon ochiq matnda saqlamang. Uni oddiy shifrlash bilan ham saqlamang (shifrni ochish mumkin). To'g'ri yechim โ€” bir tomonlama hashlash (hashing): natijadan asl parolni tiklab bo'lmaydi.

Hashlash, salt va bcrypt

Hash funksiyasi โ€” bu istalgan matndan qat'iy uzunlikdagi "barmoq izi" yaratadigan bir tomonlama funksiya. Asosiy g'oya: parolni saqlamaymiz, uning hashini saqlaymiz. Foydalanuvchi kirganda, kiritgan parolining hashini hisoblab, saqlangan hash bilan solishtiramiz.

Lekin oddiy hash (masalan, SHA-256) parollar uchun yetarli emas, chunki:

Yechim ikki qismdan iborat:

bcrypt โ€” eng ko'p ishlatiladigan yechimlardan biri. U saltni avtomatik yaratadi va uni hashning ichiga joylashtiradi:

const bcrypt = require('bcrypt');

// Ro'yxatdan o'tishda: parolni hashlaymiz
async function royxatdanOtish(email, parol) {
  const saltDarajasi = 12; // 'cost factor' โ€” qancha katta bo'lsa, shuncha sekin (xavfsiz)
  const hash = await bcrypt.hash(parol, saltDarajasi);

  // Bazaga FAQAT hashni saqlaymiz, asl parolni EMAS
  await db.foydalanuvchiYarat({ email, parolHash: hash });
}
// Tizimga kirishda: kiritilgan parolni saqlangan hash bilan solishtiramiz
async function tizimgaKirish(email, parol) {
  const foydalanuvchi = await db.foydalanuvchiTop({ email });

  // Foydalanuvchi topilmasa ham, mavhum javob beramiz (quyida tushuntiriladi)
  if (!foydalanuvchi) {
    return { muvaffaqiyat: false, xato: 'Email yoki parol xato' };
  }

  // bcrypt saltni hashdan o'zi ajratib oladi va solishtiradi
  const togri = await bcrypt.compare(parol, foydalanuvchi.parolHash);
  if (!togri) {
    return { muvaffaqiyat: false, xato: 'Email yoki parol xato' };
  }

  return { muvaffaqiyat: true, foydalanuvchi };
}
Diqqat qiling: xato xabari doim bir xil โ€” "Email yoki parol xato". Agar "Bunday email yo'q" va "Parol xato" deb alohida javob bersangiz, bu qaysi emaillar ro'yxatdan o'tganini fosh qiladi. Bir xil, mavhum xabar berish โ€” bu foydalanuvchi ro'yxatga olinganini fosh qilmaslik himoyasidir.
bcrypt.compare ni ishlatishga majburmiz โ€” hashlarni === bilan solishtirish ishlamaydi, chunki har safar yangi salt tufayli hash boshqacha bo'ladi. compare saltni hashning ichidan o'qib, to'g'ri solishtiradi.

Foydalanuvchi holatini saqlash: HTTP holatsiz

HTTP protokoli holatsiz (stateless): har bir so'rov mustaqil, server oldingi so'rovni "eslamaydi". Unda foydalanuvchi bir marta login qilgach, keyingi so'rovlarda uni qanday taniymiz? Ikki asosiy yondashuv bor: sessiya (cookie) va token.

1-yondashuv: Sessiya (cookie asosida)

Sessiya yondashuvida holat serverda saqlanadi:

  1. Foydalanuvchi login qiladi, server uni tekshiradi;
  2. Server tasodifiy, taxmin qilib bo'lmaydigan sessiya ID yaratadi va uni o'z xotirasida (yoki Redis, bazada) foydalanuvchi bilan bog'lab saqlaydi;
  3. Sessiya ID cookie sifatida brauzerga yuboriladi;
  4. Brauzer keyingi har bir so'rovda bu cookie'ni avtomatik jo'natadi;
  5. Server cookie'dagi ID orqali foydalanuvchini topadi.
const session = require('express-session');

app.use(session({
  secret: process.env.SESSION_SECRET, // sirni .env dan olamiz
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,  // JavaScript cookie'ni o'qiy olmaydi (XSS'dan himoya)
    secure: true,    // faqat HTTPS orqali yuboriladi
    sameSite: 'lax', // CSRF'dan himoya (keyingi darsda)
    maxAge: 1000 * 60 * 60 // 1 soat
  }
}));

Afzalligi: server sessiyani istagan vaqtda bekor qila oladi (masalan, "barcha qurilmalardan chiqish"). Kamchiligi: server har bir sessiyani xotirada saqlashi kerak, bu ko'p serverli tizimlarda qo'shimcha muvofiqlashtirishni talab qiladi.

2-yondashuv: Token (masalan JWT)

Token yondashuvida holat mijozda saqlanadi. Server imzolangan token beradi, mijoz uni har so'rovda qaytaradi, server esa imzoni tekshirib, tokenga ishonadi (bazaga qaramasdan). JWT โ€” keyingi darsning to'liq mavzusi.

Qaysi biri to'g'ri? Bu bahsli mavzu. An'anaviy server-render qilingan veb-saytlar uchun sessiya-cookie ko'pincha soddaroq va xavfsizroq. Turli mijozlar (mobil ilova + web) xizmat qiladigan API uchun tokenlar qulayroq. Ikkalasini ham to'g'ri sozlash mumkin.

Xavfsiz cookie: HttpOnly, Secure, SameSite

Agar cookie ishlatsangiz (sessiya yoki tokenni cookie'da saqlash), uni to'g'ri sozlash muhim. Uch asosiy atribut:

HttpOnly

HttpOnly belgilangan cookie'ni JavaScript o'qiy olmaydi (document.cookie orqali ko'rinmaydi). Bu XSS hujumida cookie o'g'irlanishining oldini oladi: hattoki sahifaga zararli skript kirsa ham, u sessiya cookie'ni o'qiy olmaydi.

Secure

Secure belgilangan cookie faqat HTTPS orqali yuboriladi. Bu cookie'ning shifrlanmagan tarmoqda ochiq holda o'tishining oldini oladi.

SameSite

SameSite cookie'ning boshqa saytlardan kelgan so'rovlarda yuborilishini boshqaradi. Bu CSRF hujumidan himoyaning asosiy qismidir (keyingi darsda batafsil):

// Cookie'ni to'g'ri sozlash misoli (Express)
res.cookie('sessionId', sessionId, {
  httpOnly: true,   // JS o'qiy olmaydi
  secure: true,     // faqat HTTPS
  sameSite: 'lax',  // CSRF himoyasi
  maxAge: 3600000,  // 1 soat (millisekundda)
  path: '/'
});
Bu uch atribut birga ishlaganda kuchli himoya beradi, ammo hech biri o'zicha yetarli emas. HttpOnly XSS ta'sirini kamaytiradi, lekin XSS'ning o'zini oldini olmaydi โ€” kirishni tozalash (keyingi dars) baribir zarur.

Xulosa