Web xavfsizligi va autentifikatsiya

JWT (JSON Web Token)

JWT (JSON Web Token) — bu ma'lumotni imzolangan, ixcham va o'zini-o'zi tavsiflaydigan token sifatida uzatish standarti. U zamonaviy API'larda autentifikatsiya uchun keng qo'llaniladi. Ushbu darsda JWT tuzilishini, uning imzo orqali qanday himoyalanishini va Express'da login'dan himoyalangan route'gacha bo'lgan to'liq oqimni ko'rib chiqamiz.

JWT nima va u nimani hal qiladi?

Oldingi darsda ko'rgan edik: HTTP holatsiz, shuning uchun har so'rovda foydalanuvchini qayta taniymiz. JWT bu muammoni imzolangan token orqali hal qiladi. Server login vaqtida foydalanuvchi ma'lumotini o'z ichiga olgan tokenni imzolaydi va mijozga beradi. Mijoz uni har so'rovda qaytaradi. Server imzoni tekshiradi — agar imzo to'g'ri bo'lsa, token ichidagi ma'lumotga ishonadi.

Muhim jihat: token ichidagi ma'lumot shifrlanmagan, lekin imzolangan. Ya'ni har kim token ichini o'qiy oladi, ammo uni server sirini bilmasdan o'zgartira olmaydi — o'zgartirilsa, imzo mos kelmaydi.

JWT payload'iga maxfiy ma'lumot (parol, karta raqami) qo'ymang. Payload oddiy kodlash bilan yozilgan, shifrlanmagan — uni har kim ochib o'qiy oladi. Payload'da faqat identifikator va rol kabi maxfiy bo'lmagan ma'lumot bo'lsin.

JWT tuzilishi: uch qism

JWT nuqta bilan ajratilgan uchta qismdan iborat: header.payload.signature. Har bir qism alohida kodlangan matndir:

// JWT quyidagicha ko'rinadi (uch qism nuqta bilan ajratilgan):
// xxxxx.yyyyy.zzzzz
//   |     |     |
// header payload signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJyb2wiOiJ1c2VyIn0.aBcDeFg...

1. Header (sarlavha)

Qanday algoritm bilan imzolanganini ko'rsatadi:

{
  "alg": "HS256",
  "typ": "JWT"
}

2. Payload (foydali yuk)

Token ichidagi ma'lumot — "claims" deb ataladi. Bu foydalanuvchi identifikatori, roli va muddati kabi maydonlar:

{
  "sub": "123",        // foydalanuvchi ID (subject)
  "rol": "user",
  "iat": 1719800000,    // yaratilgan vaqt (issued at)
  "exp": 1719803600     // amal qilish muddati (expiration)
}

3. Signature (imzo)

Header va payload'ni maxfiy kalit bilan birga hashlashdan hosil bo'ladi. Imzo — bu tokenning butunligini kafolatlaydigan qism. Agar biror bayt o'zgartirilsa, imzo mos kelmay qoladi:

// Kontseptual tarzda imzo shunday hisoblanadi:
// HMACSHA256(
//   base64url(header) + '.' + base64url(payload),
//   MAXFIY_KALIT
// )
//
// MAXFIY_KALIT faqat serverda bo'ladi. Shuning uchun
// hech kim to'g'ri imzoni server sirisiz yasay olmaydi.
"Kodlash" (base64url) — bu shifrlash EMAS. U shunchaki ma'lumotni matn ko'rinishiga o'tkazadi, uni istagan kishi qaytarib o'qiy oladi. Tokenni himoya qiladigan yagona narsa — bu imzo, chunki uni yasash uchun maxfiy kalit kerak.

Express'da JWT: login → token

Endi to'liq oqimni ko'ramiz. Avval foydalanuvchi login qiladi, biz parolni tekshiramiz va muvaffaqiyatli bo'lsa, imzolangan token beramiz. Bu yerda jsonwebtoken kutubxonasi ishlatiladi:

const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

// Maxfiy kalit .env faylidan olinadi, kodga yozilmaydi!
const JWT_SIR = process.env.JWT_SECRET;

app.post('/login', async (req, res) => {
  const { email, parol } = req.body;

  const foydalanuvchi = await db.foydalanuvchiTop({ email });
  // Bir xil mavhum xato (foydalanuvchi mavjudligini fosh qilmaslik)
  if (!foydalanuvchi || !(await bcrypt.compare(parol, foydalanuvchi.parolHash))) {
    return res.status(401).json({ xato: 'Email yoki parol xato' });
  }

  // Tokenni imzolaymiz. Payload'ga MAXFIY ma'lumot qo'ymaymiz.
  const token = jwt.sign(
    { sub: foydalanuvchi.id, rol: foydalanuvchi.rol },
    JWT_SIR,
    { expiresIn: '1h' } // token 1 soatdan keyin amal qilmaydi
  );

  res.json({ token });
});
expiresIn ni albatta belgilang. Muddatsiz token o'g'irlansa, u abadiy yaroqli qoladi. Qisqa muddat (masalan, 15 daqiqa - 1 soat) o'g'irlangan tokenning zarar oynasini kichraytiradi.

Tokenni tekshirish: himoyalangan route middleware

Endi himoyalangan route'larni yaratamiz. Har bir himoyalangan so'rovda mijoz tokenni odatda Authorization sarlavhasida Bearer sxemasi bilan yuboradi. Middleware uni tekshiradi:

function tokenTekshir(req, res, next) {
  const sarlavha = req.headers['authorization'];

  // 'Bearer <token>' formatidan tokenni ajratib olamiz
  if (!sarlavha || !sarlavha.startsWith('Bearer ')) {
    return res.status(401).json({ xato: 'Token topilmadi' });
  }
  const token = sarlavha.slice(7); // 'Bearer ' dan keyingi qism

  try {
    // verify imzoni VA muddatni tekshiradi. Xato bo'lsa, exception tashlaydi.
    const payload = jwt.verify(token, JWT_SIR);
    req.foydalanuvchi = payload; // { sub, rol, iat, exp }
    next();
  } catch (e) {
    // TokenExpiredError yoki JsonWebTokenError
    return res.status(401).json({ xato: 'Token yaroqsiz yoki muddati o'tgan' });
  }
}
// Himoyalangan route: faqat yaroqli token bilan kirish mumkin
app.get('/api/profil', tokenTekshir, async (req, res) => {
  // req.foydalanuvchi.sub — tokendan olingan foydalanuvchi ID
  const foydalanuvchi = await db.foydalanuvchiTopById(req.foydalanuvchi.sub);
  res.json({ email: foydalanuvchi.email, rol: foydalanuvchi.rol });
});

// Avtorizatsiya bilan: faqat admin
function faqatAdmin(req, res, next) {
  if (req.foydalanuvchi.rol !== 'admin') {
    return res.status(403).json({ xato: 'Ruxsat yo'q' });
  }
  next();
}
app.delete('/api/foydalanuvchi/:id', tokenTekshir, faqatAdmin, async (req, res) => {
  await db.foydalanuvchiOchir(req.params.id);
  res.json({ muvaffaqiyat: true });
});
jwt.verify ni doim try/catch ichida chaqiring. Muddati o'tgan yoki soxta token exception tashlaydi. Agar verify o'rniga decode ishlatsangiz — bu xato: decode imzoni tekshirmaydi, shunchaki payload'ni o'qiydi. Har doim verify ishlating.

Muddat va tokenni yangilash (refresh)

Qisqa muddatli access token xavfsizroq, lekin har soatda foydalanuvchini qayta login qildirish noqulay. Amaliyotda ko'pincha ikki tokenli sxema ishlatiladi:

Access token muddati tugaganda, mijoz refresh token bilan yangi access token so'raydi. Refresh tokenni serverda kuzatib borish uni bekor qilish (masalan, chiqishda) imkonini beradi.

Bu bilan JWT'ning "holatsizlik" afzalligining bir qismidan voz kechamiz (refresh tokenlarni bazada kuzatamiz), lekin buning evaziga xavfsizlikni oshiramiz. Xavfsizlik va qulaylik o'rtasidagi bu muvozanat — amaliy loyihalarda odatiy holdir.

JWT: afzallik va kamchiliklari

JWT sehrli tayoqcha emas. Uni to'g'ri joyda ishlatish uchun kuchli va zaif tomonlarini biling:

Afzalliklari

Kamchiliklari

Tokenni localStorage'da saqlash keng tarqalgan, lekin xavfli: XSS bo'lsa, zararli skript localStorage'ni o'qiy oladi va tokenni o'g'irlaydi. Xavfsizroq variant — tokenni HttpOnly cookie'da saqlash (JS o'qiy olmaydi), ammo bu holda CSRF himoyasini (keyingi darsda) qo'shish kerak. Har ikki yondashuvning o'ziga xos xavfi bor.

Xulosa