JWT — yaratish, tekshirish, access va refresh token

~25 daq · Express · Autentifikatsiya va Xavfsizlik

Muammodan boshlaylik

Foydalanuvchi login qildi. Keyingi so'rovlarda server uni qanday taniydi?

Eng oddiy yechim — har so'rovda login va parol yuborish. Lekin bu xavfli: parol har so'rovda tarmoqda yuriladi, ushlab olinishi mumkin.

Ikkinchi yechim — session. Login qilganda server "session" yaratadi, ID ni cookie ga saqlaydi. Keyingi so'rovlarda cookie yuboriladi, server session ni topadi. Lekin bu ham muammo: server xotirasida session saqlanadi, bir nechta server bo'lsa — har biri o'z sessionini bilmaydi.

JWT — uchinchi yechim. Server hech narsa saqlamaydi. Foydalanuvchiga token beriladi, u har so'rovda o'sha tokenni yuboradi. Server tokenni tekshiradi — ichida kim ekani yozilgan.


JWT nima?

JWT — JSON Web Token. Uch qismdan iborat, nuqta bilan ajratilgan:

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.abc123
      ↑                      ↑               ↑
   Header               Payload           Signature

Header — algoritm haqida ma'lumot.

Payload — asosiy ma'lumot. Foydalanuvchi ID si, roli, token muddati — bularning hammasi shu yerda. Bu qism ochiq — istalgan kishi o'qiy oladi. Shuning uchun parol, kredit karta raqami kabi maxfiy ma'lumotlar yozilmaydi.

Signature — imzo. Header + Payload + maxfiy kalit bilan hisoblangan. Bu imzo tufayli token o'zgartirilganini bilish mumkin. Kimdir payload ni o'zgartirib, o'zini admin qilib ko'rsatmoqchi bo'lsa — imzo mos kelmaydi, server rad etadi.

Hayotiy o'xshatma: pasport. Ichida kim ekani yozilgan (payload), davlat muhri bor (signature). Muhrsiz pasport — soxta. Muhri bor pasport — ishonchli.

npm install jsonwebtoken

Token yaratish

const jwt = require('jsonwebtoken');

const SECRET = process.env.JWT_SECRET; // maxfiy kalit — .env da

function createToken(userId, role) {
  return jwt.sign(
    { userId, role },  // payload — tokenga yoziladigan ma'lumot
    SECRET,
    { expiresIn: '15m' } // 15 daqiqadan keyin eskiradi
  );
}

const token = createToken(1, 'user');
// 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9...'

expiresIn — token muddati. '15m' — 15 daqiqa, '1h' — 1 soat, '7d' — 7 kun.


Token tekshirish

function verifyToken(token) {
  try {
    const decoded = jwt.verify(token, SECRET);
    return decoded; // { userId: 1, role: 'user', iat: ..., exp: ... }
  } catch (err) {
    return null; // token yaroqsiz yoki eskirgan
  }
}

jwt.verify ikki holda xato chiqaradi: token o'zgartirilgan bo'lsa yoki muddati o'tgan bo'lsa. Ikkalasi ham null qaytariladi — foydalanuvchi qayta login qilishi kerak.


Login endpoint

const bcrypt = require('bcrypt');

router.post('/login', async (req, res, next) => {
  try {
    const { email, password } = req.body;

    // Foydalanuvchini topish
    const result = await pool.query(
      'SELECT * FROM users WHERE email = $1',
      [email]
    );
    const user = result.rows[0];

    if (!user) {
      return res.status(401).json({ message: 'Email yoki parol noto\'g\'ri' });
    }

    // Parolni tekshirish
    const isValid = await bcrypt.compare(password, user.password_hash);
    if (!isValid) {
      return res.status(401).json({ message: 'Email yoki parol noto\'g\'ri' });
    }

    // Token yaratish
    const token = jwt.sign(
      { userId: user.id, role: user.role },
      SECRET,
      { expiresIn: '15m' }
    );

    res.json({ token });
  } catch (err) {
    next(err);
  }
});

Nima uchun "Email yoki parol noto'g'ri" — ikkalasi uchun bir xil xabar? Agar "Email topilmadi" deb yozilsa — hacker qaysi emaillar mavjudligini bilib oladi.


Auth middleware

Token har so'rovda Authorization headerida yuboriladi:

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

Middleware bu tokenni tekshiradi:

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

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ message: 'Token kerak' });
  }

  const token   = authHeader.split(' ')[1];
  const decoded = verifyToken(token);

  if (!decoded) {
    return res.status(401).json({ message: 'Token yaroqsiz yoki eskirgan' });
  }

  req.user = decoded; // { userId, role }
  next();
}

Access va Refresh token — nima uchun ikki token?

Access token muddati qisqa — 15 daqiqa. Nima uchun? Agar token o'g'irlansa — 15 daqiqadan keyin o'zi eskiradi. Uzoq muddat bo'lsa — o'g'irlangan token uzoq vaqt ishlatilishi mumkin.

Lekin 15 daqiqada bir marta qayta login qilish — foydalanuvchi uchun noqulay.

Yechim — ikki token:

Access token — qisqa muddat (15 daqiqa). Har so'rovda yuboriladi.

Refresh token — uzun muddat (7-30 kun). Faqat access token yangilash uchun ishlatiladi. Xavfsizroq joyda saqlanadi (httpOnly cookie).

Foydalanuvchi login qildi
    ↓
Access token (15 daqiqa) + Refresh token (7 kun) berildi
    ↓
Har so'rovda access token yuboriladi
    ↓
15 daqiqadan keyin access token eskirdi
    ↓
Refresh token bilan yangi access token so'raladi
    ↓
Yangi access token berildi (refresh token hali amal qiladi)
router.post('/login', async (req, res, next) => {
  try {
    // ... foydalanuvchini tekshirish ...

    const accessToken = jwt.sign(
      { userId: user.id, role: user.role },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

    const refreshToken = jwt.sign(
      { userId: user.id },
      process.env.JWT_REFRESH_SECRET, // alohida maxfiy kalit
      { expiresIn: '7d' }
    );

    // Refresh token — httpOnly cookie da (JavaScript o'qiy olmaydi)
    res.cookie('refreshToken', refreshToken, {
      httpOnly: true,
      secure:   process.env.NODE_ENV === 'production',
      maxAge:   7 * 24 * 60 * 60 * 1000, // 7 kun
    });

    res.json({ accessToken });
  } catch (err) {
    next(err);
  }
});

// Access token yangilash
router.post('/refresh', (req, res) => {
  const refreshToken = req.cookies.refreshToken;

  if (!refreshToken) {
    return res.status(401).json({ message: 'Refresh token kerak' });
  }

  try {
    const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);

    const accessToken = jwt.sign(
      { userId: decoded.userId },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

    res.json({ accessToken });
  } catch {
    res.status(401).json({ message: 'Refresh token yaroqsiz' });
  }
});

Tez-tez uchraydigan xatolar

Maxfiy kalitni kod ichiga yozish:

// Xato — GitHub ga chiqsa, hamma token yaratishi mumkin:
jwt.sign(payload, 'mysecret');

// To'g'ri:
jwt.sign(payload, process.env.JWT_SECRET);

Payload ga maxfiy ma'lumot yozish:

JWT payload — ochiq. Base64 bilan kodlangan, lekin shifrlangan emas. Istalgan kishi atob() bilan o'qiy oladi. Parol, kredit karta — yozilmaydi.

Token muddatini juda uzun qo'yish:

jwt.sign(payload, SECRET, { expiresIn: '365d' });
// Token o'g'irlansa — 1 yil ishlatilishi mumkin

Access token — qisqa (15 daqiqa — 1 soat). Refresh token — o'rtacha (7-30 kun).


Xulosa

JWT — server hech narsa saqlamaydi, token ichida kim ekani yozilgan. Header + Payload + Signature. Signature tufayli token o'zgartirilganini bilish mumkin.

Access token — qisqa muddat, har so'rovda yuboriladi. Refresh token — uzun muddat, faqat access token yangilash uchun.

Maxfiy kalit .env da, payload da maxfiy ma'lumot yozilmaydi.


Keyingi dars

21-dars: bcrypt — parolni hash qilish, solishtirish