Express: kirish va marshrutni himoyalash

Oldingi darsda foydalanuvchi hisob yaratdi va paroli hash holida bazaga tushdi. Endi u qaytib keladi va parolini yozadi.

Server ikki ish qilishi kerak: parol to’g’riligini tekshirish va “bu odam kirgan” degan faktni eslab qolish. Keyin esa ba’zi marshrutlarni faqat kirganlarga ochish.

Kirish marshruti

Parolni solishtirish uchun modelga bitta funksiya qo’shamiz. models/user.js:

function verifyPassword(password, passwordHash) {
  return bcrypt.compare(password, passwordHash);
}

function findById(id) {
  const row = database.prepare("SELECT * FROM users WHERE id = ?").get(id);
  return row ? toUser(row) : null;
}

module.exports = { findByEmail, findById, create, verifyPassword, toUser };

bcrypt.compare yozilgan parolni bazadagi hash bilan solishtiradi. Hash’dan parolni qaytarib olmaydi — kiritilgan parolni o’sha salt bilan qayta hash qilib, natijalarni tekshiradi.

Endi marshrut. routes/auth.js:

router.post("/login", async (request, response, next) => {
  try {
    const email =
      typeof request.body.email === "string" ? request.body.email.trim() : "";
    const password =
      typeof request.body.password === "string" ? request.body.password : "";

    const row = User.findByEmail(email);

    // Foydalanuvchi topilmasa ham parol tekshiruvini o'tkazamiz
    const isValid = row
      ? await User.verifyPassword(password, row.password_hash)
      : false;

    if (!isValid) {
      return response.status(401).json({ error: "Email yoki parol noto'g'ri" });
    }

    // Sessiya identifikatorini yangilaymiz
    request.session.regenerate((error) => {
      if (error) {
        return next(error);
      }

      request.session.userId = row.id;
      response.json(User.toUser(row));
    });
  } catch (error) {
    next(error);
  }
});
curl -i -c session.txt -X POST http://localhost:3000/login -H "Content-Type: application/json" -d '{"email":"nodira@example.com","password":"kuchli-parol-123"}'
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":2,"email":"nodira@example.com","createdAt":"2026-09-12 14:09:21"}

-c session.txt sessiya cookie’sini saqladi. Endi server bu curl ni taniydi.

Xato xabari umumiy bo’lishi kerak

Marshrutda bitta xabar ishlatildi: "Email yoki parol noto'g'ri". Aniqroq yozish vasvasasi kuchli, lekin bunday qilinmaydi.

Tekshirib ko’ring. Mavjud email, xato parol:

curl -i -X POST http://localhost:3000/login -H "Content-Type: application/json" -d '{"email":"nodira@example.com","password":"xato-parol-999"}'
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{"error":"Email yoki parol noto'g'ri"}

Mavjud bo’lmagan email:

curl -i -X POST http://localhost:3000/login -H "Content-Type: application/json" -d '{"email":"yoq@example.com","password":"xato-parol-999"}'
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{"error":"Email yoki parol noto'g'ri"}

Javoblar ayni bir xil. Tashqaridan qaraganda ikki holatni ajratib bo’lmaydi.

session.regenerate nima uchun

Kirish paytida sessiya id si yangilandi. Bu bitta qatorli, lekin muhim himoya.

regenerate callback qabul qiladi, shuning uchun userId va javob uning ichida yoziladi. Tashqarida yozsangiz, ular sessiya yangilanishidan oldin bajarilib, qiymat yo’qoladi.

requireAuth middleware

Endi marshrutlarni himoyalaymiz. middleware/auth.js:

// middleware/auth.js
function requireAuth(request, response, next) {
  if (!request.session.userId) {
    return response.status(401).json({ error: "Avval tizimga kiring" });
  }

  next();
}

module.exports = { requireAuth };

Bu middleware darsidagi qolipning to’g’ridan-to’g’ri qo’llanishi: shart bajarilsa next(), bajarilmasa javob va zanjir to’xtaydi.

app.js da ulash:

const { requireAuth } = require("./middleware/auth");

app.use(express.json());
app.use(session({ /* sozlamalar */ }));

// Butun tasks API'si himoyalandi
app.use("/api/tasks", requireAuth, tasksRouter);

Kirmasdan so’rang:

curl -i http://localhost:3000/api/tasks
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{"error":"Avval tizimga kiring"}

Kirgandan keyin:

curl -i -b session.txt http://localhost:3000/api/tasks
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[{"id":1,"title":"Express o'rganish","isDone":true,"createdAt":"2026-09-12 14:04:16"}]

Bitta middleware butun Router ni qopladi — beshta marshrutning har biriga alohida yozish shart emas.

401 va 403 farqi

Ikki kod chalkashtiriladi, lekin ular boshqa savolga javob beradi.

Kod Ma’nosi Misol
401 Unauthorized Kim ekaningiz noma’lum Kirmagan foydalanuvchi
403 Forbidden Kim ekaningiz ma’lum, huquqingiz yo’q Oddiy foydalanuvchi admin sahifasida

401 — “kiring”. 403 — “kirdingiz, lekin bu sizga emas”. Frontend ularni turlicha ishlaydi: birinchisida kirish sahifasiga yo’naltiradi, ikkinchisida “ruxsat yo’q” deb ko’rsatadi.

O’z ma’lumotini olish

Frontend “kim kirgan?” deb so’raydigan marshrut odatiy:

router.get("/me", (request, response) => {
  if (!request.session.userId) {
    return response.status(401).json({ error: "Avval tizimga kiring" });
  }

  response.json(User.findById(request.session.userId));
});
curl -b session.txt http://localhost:3000/me
{"id":2,"email":"nodira@example.com","createdAt":"2026-09-12 14:09:21"}

Sessiyada faqat userId saqlanadi, qolgan ma’lumot bazadan olinadi. Nega butun foydalanuvchi obyektini sessiyaga solmaymiz: email o’zgarsa yoki hisob o’chirilsa, sessiyadagi nusxa eskirib qoladi. id esa hech qachon o’zgarmaydi.

HTML sahifalar uchun himoya

API 401 JSON qaytaradi. Brauzer sahifasi uchun bu noqulay — foydalanuvchi xom JSON ko’rmasligi kerak. Ikkinchi variant yoziladi:

function requireAuthPage(request, response, next) {
  if (!request.session.userId) {
    return response.redirect("/login");
  }

  next();
}

module.exports = { requireAuth, requireAuthPage };
app.get("/dashboard", requireAuthPage, (request, response) => {
  response.render("dashboard", { pageTitle: "Mening vazifalarim" });
});

Farq bitta qatorda: JSON javob o’rniga yo’naltirish. Mantiq bir xil.

Chiqish

router.post("/logout", (request, response, next) => {
  request.session.destroy((error) => {
    if (error) {
      return next(error);
    }

    response.clearCookie("connect.sid");
    response.json({ message: "Chiqdingiz" });
  });
});
curl -b session.txt -X POST http://localhost:3000/logout
{"message":"Chiqdingiz"}
curl -i -b session.txt http://localhost:3000/me
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{"error":"Avval tizimga kiring"}

Eski cookie hamon curl da turibdi, lekin serverda sessiya yo’q — javob 401.

Ma’lumotni foydalanuvchiga bog’lash

Hozir kirgan har kim hamma vazifani ko’radi. To’liq tizimda har vazifa o’z egasiga tegishli bo’ladi.

Buning uchun yangi migratsiya kerak:

// migrations/004-add-task-user.js
module.exports = {
  name: "004-add-task-user",
  up(database) {
    database.exec(
      "ALTER TABLE tasks ADD COLUMN user_id INTEGER REFERENCES users(id)"
    );
  },
};

Model funksiyalari userId ni qabul qiladigan bo’ladi:

function getAllByUser(userId) {
  const rows = database
    .prepare("SELECT * FROM tasks WHERE user_id = ? ORDER BY id")
    .all(userId);

  return rows.map(toTask);
}

Marshrut esa sessiyadan oladi:

router.get("/", (request, response) => {
  response.json(Task.getAllByUser(request.session.userId));
});

Amaliy mashq

task-app loyihasida davom eting:

  1. models/user.js ga verifyPassword va findById ni qo’shing;
  2. POST /login ni yozib, to’g’ri parol bilan 200 va sessiya cookie olishingizni tasdiqlang;
  3. Xato parol va mavjud bo’lmagan email bilan sinab, ikkala javob ham aynan bir xil ekanini tekshiring;
  4. requireAuth middleware’ini yozib, /api/tasks ga ulang va kirmasdan 401 olishingizni ko’ring;
  5. session() middleware’ini requireAuth dan pastga ko’chiring, chiqqan xatoni o’qing, so’ng joyiga qaytaring;
  6. POST /logout ni yozib, chiqqandan keyin /me 401 berishini tasdiqlang.

3-qadam darsning asosiy xavfsizlik g’oyasi: ikki javob farq qilsa, saytingiz foydalanuvchi ro’yxatini o’zi aytib beradi.

Qisqacha xulosa

  • bcrypt.compare kiritilgan parolni hash bilan solishtiradi; asl parol hech qayerda saqlanmaydi.
  • Email topilmasa ham, parol xato bo’lsa ham — bitta umumiy xabar qaytariladi.
  • session.regenerate() kirish paytida sessiya id sini yangilab, sessiya fixation hujumini to’sadi.
  • Sessiyada faqat userId saqlanadi, qolgani bazadan o’qiladi.
  • requireAuthnext() chaqiruvchi oddiy middleware; uni Router oldiga qo’yib butun guruhni himoyalash mumkin.
  • 401 — kim ekaningiz noma’lum, 403 — huquqingiz yo’q; chiqish esa POST bilan bajarilib, session.destroy() ni chaqiradi.
  • requireAuth egalikni tekshirmaydi — yozuv kimniki ekanini so’rovda alohida tekshiring.

Keyingi darsda JWT va API token — sessiyasiz autentifikatsiya va mobil ilovalar uchun yondashuv.

Bu dars foydali bo'ldimi?

marta ko'rildi