Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ikki yondashuv
- 2.1 Stateful va stateless
- 2.2 Kanon o'lchovlari
- 2.3 Qaror daraxti
- 3. ADR: qarorni yozib qo'yish
- 3.1 Format
- 3.2 docs/AUTH-QAROR.md
- 4. Uzoq sessiyalar
- 4.1 Ikki chegara: idle va mutlaq
- 4.2 Sliding — lekin har so'rovda yozmasdan
- 4.3 "Meni eslab qol" — xavfsizlik nuqtai nazaridan
- 5. Qurilmalar ro'yxati va majburan tugatish
- 5.1 "Qayerlarda kirganman?"
- 5.2 Bitta sessiyani o'chirish va IDOR
- 5.3 Majburan tugatishning uch darajasi
- 6. Mobil ilova tokenlari
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi biri?
- 2-mashq (o'rta): qachon tugaydi?
- 3-mashq (qiyin): 60 soniyalik qadam
- 10. Loyiha qadami: eslab qolish, qurilmalar va ADR
- 10.1 Migratsiya sql/113-sessiya-eslab-qol.sql
- 10.2 Kanon topilmasi: 168:00:00
- 10.3 Kod
- 10.4 Yo'llar, sxema va config
- 10.5 Testlar
- 10.6 Tekshiruv va commit
- 11. Real ishda
- Xulosa
- Manbalar
Session vs JWT qarori va uzoq sessiyalar: ADR, "meni eslab qol" va qurilmalar ro'yxati
Qisqacha: Brauzer va API bir saytda bo'lsa — server sessiyasi yetarli va xavfsizroq: chiqish darhol ishlaydi, har qurilmani alohida o'chirish mumkin, token kichik. JWT'ning kuchi — bazasiz tekshirish va servislar orasida ishonch — bunday ilovaga kerak emas, kamchiligi (muddatigacha bekor qilib bo'lmaydi) esa kerakli funksiyalarga zid. Qaror o'lchovlar bilan ADR hujjatiga yoziladi. Uzoq sessiya uchun ikki chegara: idle (jim turish, har so'rovda suriladi) va mutlaq muddat; "meni eslab qol" ularni uzaytiradi.
Bu darsda
- Stateful (sessiya) va stateless (JWT) yondashuvni kanon o'lchovlari bilan solishtirasiz.
- Arxitektura qarorini ADR (Architecture Decision Record) shaklida yozishni o'rganasiz.
- Idle va mutlaq muddatni, sliding expiration va "meni eslab qol" ni farqlaysiz.
- Qurilmalar ro'yxati va bitta qurilmadan chiqishni IDOR'siz yozasiz.
vazifalar-apiga 113-migratsiyani,docs/AUTH-QAROR.mdni va ikkita yangi testni qo'shasiz.
Oldin bilishingiz kerak: Session-based auth, JWT anatomiyasi, Access va refresh token strategiyasi, Tokenni qayerda saqlash va CSRF.
Oldingi to'rt darsda ikki yo'lni ham qurdik. Session-based auth — vazifalar-api ga kirdi. JWT va refresh rotatsiya — JWT anatomiyasi va Access va refresh token strategiyasi darslarida haqiqiy kod bilan sinaldi, lekin loyihaga qo'shilmadi. Nega? Bugun shu qarorni ochiq, o'lchovlar bilan yozamiz — va keyin uni uzoq sessiyalar bilan to'ldiramiz.
1. Nega bu kerak?
Internetda "JWT yoki sessiya?" degan bahs tugamaydi. Bir maqola "sessiya eskirdi, JWT zamonaviy" deydi, boshqasi "JWT'ni sessiya uchun ishlatmang" deydi. Ikkalasi ham o'z sharoitida to'g'ri. Xato — sharoitni ko'rmasdan, modaga qarab tanlash.
Yaxshi jamoada bunday savol bir marta, ochiq hal qilinadi va yozib qo'yiladi: qaysi variantlar ko'rildi, qaysi raqamlar o'lchandi, nima tanlandi va qachon qayta ko'rib chiqiladi. Olti oydan keyin yangi dasturchi "nega JWT emas?" deb so'rasa, javob bitta faylda turadi.
Ikkinchi muammo — foydalanuvchi. Telefonda vazifalar ni har kuni ochadigan Malika har hafta parol kiritishni xohlamaydi. Kutubxona kompyuteridan kirgan Jasur esa brauzerni yopganda sessiya o'chishini xohlaydi. Ikkalasiga bitta muddat to'g'ri kelmaydi.
2. Ikki yondashuv
2.1 Stateful va stateless
- Stateful (holatli) — server foydalanuvchini eslab qoladi: bazada sessiya qatori bor, brauzerda faqat tasodifiy kalit. Har so'rovda server kalitni bazadan qidiradi. Xuddi kiyim ilgichidagi raqamli jeton: jetonda hech narsa yozilmagan, kiyim ilgichda.
- Stateless (holatsiz) — server hech narsani eslamaydi: kim ekaningiz, rolingiz va muddat tokenning o'zida yozilgan va imzolangan (JWT). Server faqat imzoni tekshiradi. Xuddi suratli guvohnoma: qo'riqchi ro'yxatga qaramaydi, guvohnomaning o'ziga qaraydi.
Guvohnomaning muammosi ham shu yerda: uni qaytarib olib bo'lmaydi. Xodim ishdan bo'shatilsa ham, guvohnomadagi muddat tugaguncha u eshikdan o'taveradi — qo'riqchida "bekor qilinganlar ro'yxati" bo'lmasa. Ro'yxat paydo bo'lsa esa — biz yana holatli tizimga qaytdik.
2.2 Kanon o'lchovlari
Har ikkala yo'l bitta mashinada (i5-12500H, Node 24.21.0, 2026-10-27) o'lchangan. Avval — har so'rovda "bu kim?" savoliga javob narxi:
- JWT (jose, EdDSA)bazasiz0,213 ms
- connect-redisRedis0,872 ms
- connect-pg-simplePostgres1,163 ms
- kanon tekshir()Postgres, JOIN1,605 ms
Manba: Kanon o'lchovi (IlmHamroh, 2026-10): Node 24.21.0, PostgreSQL 18.6, Redis 8.10.2, jose 6.2.12, i5-12500H, 2026-10-27
JWT tekshiruvi 7.5 marta tez — bazaga bormaydi. Lekin raqamlarni masshtab bilan o'qing: farq 1.4 ms. Postgres'dagi har bir vazifa so'rovi, JSON yasash va tarmoq shundan ko'p vaqt oladi. Kanon sessiyasi Redis'dagidan biroz sekinroq, chunki u bitta JOIN bilan foydalanuvchini ham olib keladi — keyingi so'rov kerak bo'lmaydi.
Endi qolgan mezonlar:
| Mezon | Server sessiyasi | Access JWT + refresh |
|---|---|---|
| Chiqish, bloklash | darhol | refresh darhol, access 15 daqiqagacha yashaydi |
| O'g'irlangan token | sessiya bekor | reuse detection; 2 tab poygasida soxta chiqarish |
| Har so'rov sarlavhasi | cookie 58 belgi | access 298 belgi |
| Baza | sessiyalar (hash) |
refresh_tokenlar + kalit aylantirish |
| Xato qilish imkoni | kam | ko'p: alg, aud, exp, kalitlar, oila |
Jadvaldagi har qatorning orqasida tajriba bor. Access va refresh token strategiyasi darsida ko'rganimizdek, "barcha qurilmalardan chiqish" refresh tokenlarni o'ldirdi, lekin access tokenlar o'z muddatigacha ishlab turdi. Bir foydalanuvchining ikki tabi bir vaqtda yangilaganda rotatsiya buni o'g'irlik deb bildi va foydalanuvchini chiqarib yubordi. JWT anatomiyasi darsida esa alg: none va kuchsiz sir bilan bog'liq tuzoqlarni ko'rdik.
58 belgi qayerdan kelgani — tekshirib ko'ring:
const token = "A".repeat(43); // 32 bayt base64url — 43 belgi
console.log(`__Host-sessiya=${token}`.length);Konsolda:
582.3 Qaror daraxti
O'lchovlar bitta savolga olib keladi: bizga JWT'ning kuchi kerakmi?
flowchart TD
S{Kim API'ni chaqiradi?} -->|bir saytdagi brauzer| B[Server sessiyasi + HttpOnly cookie]
S -->|mobil ilova| M[Qisqa access + refresh yoki opaque token]
S -->|boshqa servis| J{Bazasiz tekshirish kerakmi?}
J -->|ha| JW[Qisqa JWT, aniq aud]
J -->|yo'q| K[API kaliti yoki HMAC]
S -->|tashqi ilova foydalanuvchi nomidan| O[OAuth 2.0 token]Diagrammada eng muhimi — birinchi shox. Bizning holatimiz shu: frontend va API bir saytda. Sessiya bu yerda soddaroq, tezroq bekor qilinadi va xavf yuzasi kichik. Qolgan shoxlar — API kalitlar va servislararo autentifikatsiya va OAuth 2.0 va OpenID Connect nazariyasi darslarida.
Tekshirib ko'ring: Bitta Node serveri o'rniga beshta server bo'lsa (load balancer orqasida), sessiya baribir ishlaydimi?
Javob
Ishlaydi, chunki sessiya bitta umumiy bazada (Postgres yoki Redis), serverning xotirasida emas. Har server so'rovni olib, o'sha bazadan tekshiradi. "Sessiya masshtablanmaydi" degan gap xotirada (MemoryStore) saqlangan sessiyaga tegishli. Masshtab muammosi faqat juda ko'p servislar va juda katta yuklamada boshlanadi — u yerda JWT'ning bazasiz tekshiruvi haqiqatan foyda beradi.
3. ADR: qarorni yozib qo'yish
3.1 Format
ADR (Architecture Decision Record — arxitektura qarori yozuvi) — bitta qaror uchun bitta qisqa fayl. Formatni 2011-yilda Michael Nygard taklif qilgan; uning asosiy bo'limlari: kontekst, qaror, oqibatlar. Kanon o'z variantini oldi:
| Bo'lim | Savol |
|---|---|
| Kontekst | qanday sharoitda va qanday talablar bilan qaror qabul qilinyapti? |
| Variantlar | qaysi yo'llar ko'rildi va ular qanday o'lchandi? |
| Qaror | nima tanlandi va asosiy sababi? |
| Qachon qayta ko'riladi | qaysi o'zgarish bu qarorni bekor qiladi? |
Oxirgi bo'lim eng ko'p unutiladi va eng qimmati. U qarorni "abadiy haqiqat" dan "shu sharoitdagi eng yaxshi tanlov" ga aylantiradi.
3.2 docs/AUTH-QAROR.md
Kanon ADR'ini to'liq ko'rsatamiz. Jadvaldagi o'lchov havolalarini darsdagi nomlar bilan almashtirdik va satrlarni qisqartirdik, mazmun o'zgarmagan:
# ADR: brauzer uchun server sessiyasi (JWT emas)
Holat: **qabul qilingan** (2026-10-27).
## Kontekst
`vazifalar` frontendi va `vazifalar-api` bir saytda ishlaydi:
dev'da Vite proxy, deploy'da bitta domen ostida nginx. Mijoz —
brauzer (keyin Telegram bot — u API kaliti bilan). Talablar: chiqish
darhol ishlasin, "barcha qurilmalardan chiqish", qurilmalar
ro'yxati, XSS token o'g'irlay olmasin.
## Variantlar (ikkalasi haqiqiy kod bilan o'lchangan)
| Mezon | Server sessiyasi | Access JWT + refresh |
|---|---|---|
| Har so'rovda tekshirish | 1 so'rov, 1.6 ms | imzo, 0.21 ms |
| Chiqish / bloklash | darhol | access 15 daqiqagacha |
| O'g'irlangan token | sessiya bekor | reuse detection, poyga |
| Saqlash joyi | HttpOnly cookie | xotira/cookie + cookie |
| Sarlavha hajmi | 58 belgi | 298 belgi |
| Baza | sessiyalar (hash) | refresh_tokenlar + JWKS |
| Kod va xavf | kam | ko'p: alg, aud, exp, kalit |
## Qaror
Brauzer uchun — **server sessiyasi** (Postgres). Bir sayt
arxitekturasida JWT'ning asosiy afzalligi (bazasiz tekshirish,
servislar orasida tarqatish) bizga kerak emas, kamchiligi (darhol
bekor qilib bo'lmaydi) esa kerakli funksiyalarga to'g'ridan zid.
## Uzoq sessiyalar
- "Meni eslab qol": cookie 30 kun, idle 7 kun — har so'rovda
suriladi (sliding, 60 s qadam bilan).
- Belgilanmasa: cookie Expires'siz, idle 12 soat, mutlaq 7 kun.
- Qurilmalar: GET /v1/auth/sessiyalar, bittasi —
DELETE /v1/auth/sessiyalar/:id, hammasi —
POST /v1/auth/chiqish-hammasi.
## Qachon qayta ko'riladi
Mobil ilova (cookie'siz) yoki boshqa servis foydalanuvchi nomidan
so'rov yuborsa — qisqa JWT (EdDSA, aniq aud) yoki OAuth 2.0 token;
dastur-dastur so'rovlari — API kaliti / HMAC.Kanon matnida "1.4 ms farq HTTP so'rovning umumiy vaqti ichida sezilmaydi" degan gap ham bor — biz uni grafik ostidagi izohda aytdik.
4. Uzoq sessiyalar
4.1 Ikki chegara: idle va mutlaq
Sessiyaning umri ikki soat bilan o'lchanadi:
- Idle timeout (jim turish chegarasi) — oxirgi so'rovdan beri qancha vaqt o'tdi. Foydalanuvchi faol bo'lsa, har so'rovda bu chegara suriladi. Buni sliding expiration (siljiydigan muddat) deyishadi.
- Absolute timeout (mutlaq chegara) — sessiya yaratilgandan beri. Faol bo'lsa ham, shundan keyin qayta kirish kerak. O'g'irlangan sessiya abadiy yashamasligi uchun.
Kanonning ikki rejimi:
| Rejim | Cookie | Idle | Mutlaq |
|---|---|---|---|
| "Meni eslab qol" | Expires — 30 kun |
7 kun | 30 kun |
| Belgilanmagan | Expires yo'q |
12 soat | 7 kun |
Expires siz cookie — sessiya cookie'si: brauzer yopilganda o'chadi (kutubxona kompyuteridagi Jasur uchun). Lekin "brauzer yopilganda" har doim ham amal qilmaydi — ko'p brauzerlar "yopilgan tablarni tiklash" rejimida sessiya cookie'larini ham qaytaradi. Shuning uchun serverdagi 12 soatlik idle va 7 kunlik mutlaq chegara — haqiqiy himoya, cookie'ning Expires i emas.
Kanon tekshiruvining shartlarini toza funksiya bilan sinaymiz:
const SOAT = 3_600_000;
const KUN = 24 * SOAT;
// Kanon tekshiruvining shartlari — bazasiz, toza funksiya
function holat(s, hozir) {
if (s.bekor) return "bekor qilingan";
if (hozir >= s.yaratilgan + s.muddat) return "mutlaq tugadi";
if (hozir - s.oxirgi >= s.idle) return "idle tugadi";
return "tirik";
}
const ESLAB = { yaratilgan: 0, muddat: 30 * KUN, idle: 7 * KUN };
const QISQA = { yaratilgan: 0, muddat: 7 * KUN, idle: 12 * SOAT };
const VAZIYATLAR = [
["13 soat jim", 0, 13 * SOAT],
["har kuni, 6-kun", 5.75 * KUN, 6 * KUN],
["har kuni, 10-kun", 9.75 * KUN, 10 * KUN],
["8 kun jim", 0, 8 * KUN],
["har kuni, 31-kun", 30.75 * KUN, 31 * KUN],
];
console.log("vaziyat".padEnd(17), "eslab qol".padEnd(14), "qisqa");
for (const [nom, oxirgi, hozir] of VAZIYATLAR) {
const a = holat({ ...ESLAB, oxirgi }, hozir);
const b = holat({ ...QISQA, oxirgi }, hozir);
console.log(nom.padEnd(17), a.padEnd(14), b);
}Konsolda:
vaziyat eslab qol qisqa
13 soat jim tirik idle tugadi
har kuni, 6-kun tirik tirik
har kuni, 10-kun tirik mutlaq tugadi
8 kun jim idle tugadi mutlaq tugadi
har kuni, 31-kun mutlaq tugadi mutlaq tugadi"har kuni" — foydalanuvchi har kuni kiradi, oxirgi so'rovi 6 soat oldin. Ikkinchi va uchinchi qatorni solishtiring: har kuni kiradigan Malika ham "qisqa" rejimda 7-kundan keyin qayta kiradi — mutlaq chegara. "Eslab qol" rejimida u 30 kun tinch ishlaydi. Oxirgi qator: 30 kundan keyin hamma qayta kiradi.
4.2 Sliding — lekin har so'rovda yozmasdan
Idle chegarani surish uchun oxirgi_faollik yangilanishi kerak. Har so'rovda UPDATE — bazaga ortiqcha yozuv. Kanon qoidasi: faqat 60 soniyadan eski bo'lsa yozadi (Session-based auth darsidan). Narxi — bazadagi faollik 60 soniyagacha "orqada" qolishi mumkin. 7 kunlik idle uchun bu ahamiyatsiz. Uchinchi mashqda yozuvlar sonini hisoblaysiz.
4.3 "Meni eslab qol" — xavfsizlik nuqtai nazaridan
Belgi qo'yilsa, cookie 30 kun yashaydi va o'g'irlansa 30 kun ishlaydi. Shuning uchun:
- belgi sukut bo'yicha o'chiq (
eslabQol—false): foydalanuvchi uni ongli tanlaydi; - uzoq sessiyada ham mutlaq chegara bor — 30 kun;
- foydalanuvchi o'z sessiyalarini ko'rib, keraksizini o'chira oladi (keyingi bo'lim);
- parol tiklanganda hamma sessiyalar bekor bo'ladi (Email tasdiqlash va parol tiklash oqimlari).
Muhim nozik joy: kanon sxemasida migratsiyadan oldingi sessiyalar uchun sukut eslab_qol = true. Bu xulqni o'zgartirmaslik uchun: 113-migratsiyagacha hamma sessiya 30 kunlik edi. Yangi kirishlarda esa sukut — false, u sxemada (Zod) belgilangan.
Tekshirib ko'ring: Nega "eslab qolinmagan" sessiyaning mutlaq chegarasi bazada saqlanadi, cookie'ning o'zida emas?
Javob
Cookie — mijozdagi narsa. Uning Expires i yo'q, demak brauzer qachon o'chirishini server bilmaydi: brauzer tablarni tiklasa, cookie haftalab yashashi mumkin. Hujumchi esa cookie'ni o'z dasturiga ko'chirib, Expires ga umuman qaramaydi. Server faqat o'zi saqlagan narsaga ishonadi — tugash_vaqti va idle_muddat bazada.
5. Qurilmalar ro'yxati va majburan tugatish
5.1 "Qayerlarda kirganman?"
Telegram, Google va banklarda bor bo'lim: "faol sessiyalar" (ba'zi ilovalarda "seanslar"). Unda har qurilma, oxirgi faollik va "chiqish" tugmasi. Server sessiyasida buni qilish oson — sessiyalar jadvalda turibdi. JWT bilan esa buning uchun baribir jadval kerak bo'lardi.
Kanon user_agent ni saqlaydi, ro'yxatda esa undan qisqa nom yasaydi:
function qurilma(ua) {
if (ua === null || ua === "") return "noma'lum";
const brauzer = /Edg\//.test(ua) ? "Edge"
: /OPR\//.test(ua) ? "Opera"
: /Firefox\//.test(ua) ? "Firefox"
: /Chrome\//.test(ua) ? "Chrome"
: /Safari\//.test(ua) ? "Safari"
: /curl\//.test(ua) ? "curl"
: "boshqa";
const tizim = /Android/.test(ua) ? "Android"
: /iPhone|iPad/.test(ua) ? "iOS"
: /Windows/.test(ua) ? "Windows"
: /Mac OS X/.test(ua) ? "macOS"
: /Linux/.test(ua) ? "Linux"
: "";
return tizim === "" ? brauzer : `${brauzer}, ${tizim}`;
}
const UA = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/151.0 " +
"Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0) Chrome/151.0 Safari/537.36 " +
"Edg/151.0",
"Mozilla/5.0 (Linux; Android 15) Chrome/151.0 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 19_0 like Mac OS X) " +
"Version/19.0 Mobile/15E148 Safari/604.1",
"curl/8.11.0",
null,
];
for (const ua of UA) console.log(qurilma(ua));Konsolda:
Chrome, Windows
Edge, Windows
Chrome, Android
Safari, iOS
curl
noma'lumTekshiruvlar tartibiga qarang — u tasodifiy emas. Edge'ning User-Agent satrida Chrome/ ham bor, shuning uchun Edg/ birinchi tekshiriladi. Android satrida Linux bor, iPhone satrida Mac OS X — ularning tartibi ham shuning uchun. UA satrlari namunaviy, qisqartirilgan.
Ikki muhim gap. Birinchisi — User-Agent'ni mijoz o'zi yozadi, uni soxtalashtirish oson. Shuning uchun u faqat ko'rsatish uchun, hech qanday xavfsizlik qarori unga tayanmaydi. Ikkinchisi — ro'yxatda token ham, uning hash'i ham yo'q: javob o'g'irlansa ham undan sessiya yasab bo'lmaydi.
5.2 Bitta sessiyani o'chirish va IDOR
DELETE /v1/auth/sessiyalar/:id — "telefonimdan chiqish". Bu yerda klassik xato kutib turadi: sessiya id si — oddiy son. Agar server faqat WHERE id = $1 qilsa, Dilnoza id larni ketma-ket sinab, boshqalarning sessiyalarini o'chirib chiqadi. Bu IDOR (Insecure Direct Object Reference — obyektga himoyasiz to'g'ridan murojaat), API dunyosida BOLA deb ataladi (Session-based auth darsida vazifalar uchun ko'rgandik).
Kanon himoyasi — egasi sharti so'rovning o'zida:
UPDATE sessiyalar SET bekor_vaqti = now()
WHERE id = $1 AND foydalanuvchi_id = $2 AND bekor_vaqti IS NULL;Begona sessiya id si uchun bitta ham qator o'zgarmaydi va server 404 qaytaradi — 403 emas. 403 "bunday sessiya bor, lekin sizniki emas" degan ma'lumotni oshkor qilardi.
5.3 Majburan tugatishning uch darajasi
| Kim | Nima | Qanday |
|---|---|---|
| foydalanuvchi | bitta qurilma | DELETE /v1/auth/sessiyalar/:id |
| foydalanuvchi | hamma qurilmalar | POST /v1/auth/chiqish-hammasi |
| tizim | hamma qurilmalar | parol tiklanganda — avtomatik |
Admin tomonidan bloklash ham xuddi shu UPDATE — rollarni RBAC va resurs egaligi darsida qo'shamiz. Server sessiyasida har uchala holat keyingi so'rovdayoq kuchga kiradi.
6. Mobil ilova tokenlari
Mobil ilovada brauzer yo'q: cookie'ni ilova o'zi boshqaradi va CSRF muammosi ham yo'q. Odatdagi yo'llar:
- Opaque token (ma'nosiz tasodifiy satr) — xuddi bizning sessiya tokeni, lekin cookie o'rniga
Authorization: Bearersarlavhasida. Ilova uni telefonning xavfsiz omborida saqlaydi (iOS Keychain, Android Keystore). - Qisqa access + refresh — refresh rotatsiya bilan; ayniqsa bir nechta servis bo'lsa.
- Uchinchi tomon ilovalari uchun — OAuth 2.0 (mobil ilovalar uchun qoidalar RFC 8252 da).
Kanon bularni sinamadi — mobil ilova kursda keyinroq. ADR'dagi "Qachon qayta ko'riladi" bo'limi aynan shu holatni yozib qo'ygan: mobil ilova paydo bo'lsa — qaror qayta ko'riladi.
7. Hujumchi nigohi
Oxirgi ustun — himoya qayerda sinalgani.
| Hujum | Nima uchun ishlaydi | Himoya | Qayerda sinaldi |
|---|---|---|---|
| o'g'irlangan cookie bilan oylab yurish | muddat cheksiz yoki juda uzun | mutlaq chegara (7 / 30 kun) | holat(): "mutlaq tugadi" |
| ochiq qolgan kutubxona kompyuteri | sessiya brauzer yopilgandan keyin ham tirik | eslab qolinmasa — 12 soat idle | test: 13 soatdan keyin 401 |
DELETE /sessiyalar/<begona id> |
egasi tekshirilmaydi | AND foydalanuvchi_id = $2, 404 |
test: Dilnoza sessiyasi — 404 |
| ro'yxat javobidan token olish | javobga hash/token tushadi | ro'yxatda faqat id, vaqt, qurilma | test: javobda token yo'q |
| soxta User-Agent bilan "ishonchli qurilma" | UA'ga qaror bog'langan | UA — faqat ko'rsatish uchun | — (kod qoidasi) |
| JWT chiqqandan keyin ham ishlaydi | stateless token bekor bo'lmaydi | brauzer uchun server sessiyasi | ADR, oldingi darslar |
| idle'ni idle bilan "uzaytirish" | faqat sliding, mutlaq yo'q | ikkala chegara birga | holat(): "har kuni, 31-kun" |
8. Ko'p uchraydigan xatolar
| Xato | Nega xavfli | To'g'risi |
|---|---|---|
| "JWT zamonaviy" deb tanlash | bekor qilish va xavf yuzasi unutiladi | o'lchab, ADR'ga yozish |
| faqat idle chegara | faol hujumchi abadiy qoladi | idle + mutlaq |
| "eslab qol" sukut bo'yicha yoqiq | umumiy kompyuterda sessiya qoladi | sukut — o'chiq |
har so'rovda oxirgi_faollik yozish |
ortiqcha yozuv yuklamasi | 60 s qadam |
sessiya id bo'yicha o'chirish |
IDOR | egasi sharti SQL'da |
| begona sessiyaga 403 | borligini oshkor qiladi | 404 |
| ADR'siz qaror | "nega?" savoliga javob yo'q | bir fayl, "qachon qayta ko'riladi" bilan |
9. Mashqlar
Mashqlar kurs/mashqlar/28/11-sessiya-qarori/ da.
1-mashq (oson): qaysi biri?
Har vaziyat uchun tanlang: server sessiyasi, qisqa JWT yoki API kaliti.
vazifalarfrontendi va API bitta domen ostida.- Hisobot servisi
vazifalar-apiga har kecha ma'lumot so'raydi (foydalanuvchisiz). - Yuzlab mikroservis bitta foydalanuvchi so'rovini bir-biriga uzatadi va har biri bazaga bormasdan "kim?" ni bilishi kerak.
- Bank ilovasi: operator foydalanuvchini darhol bloklay olishi shart.
Yechim
- Server sessiyasi — bir sayt, darhol chiqish, kanon qarori.
- API kaliti (yoki HMAC imzo) — bu foydalanuvchi emas, dastur (API kalitlar va servislararo autentifikatsiya).
- Qisqa JWT — aynan JWT'ning kuchi: bazasiz tekshirish va servislar orasida ishonch. Muddat — daqiqalar.
- Server sessiyasi — darhol bloklash talab qilingan; JWT bilan bu taqiq ro'yxatini (denylist), ya'ni yana holatni talab qiladi.
2-mashq (o'rta): qachon tugaydi?
qachonTugaydi(s) funksiyasini yozing: sessiya { yaratilgan, muddat, idle, oxirgi } (ms) bo'yicha qaysi chegara birinchi kelishini va qachonligini qaytarsin: { vaqt, sabab }, sabab — "mutlaq" yoki "idle". To'rt holatni soatlarda chiqaring: qisqa rejim, hozirgina kirgan (oxirgi: 0); qisqa, 6.9-kunda faol; eslab qolingan, 2-kunda faol; eslab qolingan, 29-kunda faol. Ishora: toFixed(1) (Matn ↔ son va sonni tekshirish) va padEnd.
Yechim
const SOAT = 3_600_000;
const KUN = 24 * SOAT;
function qachonTugaydi(s) {
const mutlaq = s.yaratilgan + s.muddat;
const idle = s.oxirgi + s.idle;
return mutlaq <= idle
? { vaqt: mutlaq, sabab: "mutlaq" }
: { vaqt: idle, sabab: "idle" };
}
const QISQA = { yaratilgan: 0, muddat: 7 * KUN, idle: 12 * SOAT };
const ESLAB = { yaratilgan: 0, muddat: 30 * KUN, idle: 7 * KUN };
const SINOV = [
["qisqa, hozirgina kirdi", { ...QISQA, oxirgi: 0 }],
["qisqa, 6.9-kun faol", { ...QISQA, oxirgi: 6.9 * KUN }],
["eslab, 2-kun faol", { ...ESLAB, oxirgi: 2 * KUN }],
["eslab, 29-kun faol", { ...ESLAB, oxirgi: 29 * KUN }],
];
for (const [nom, s] of SINOV) {
const { vaqt, sabab } = qachonTugaydi(s);
const soat = (vaqt / SOAT).toFixed(1);
console.log(nom.padEnd(23), soat, "soat,", sabab);
}Konsolda:
qisqa, hozirgina kirdi 12.0 soat, idle
qisqa, 6.9-kun faol 168.0 soat, mutlaq
eslab, 2-kun faol 216.0 soat, idle
eslab, 29-kun faol 720.0 soat, mutlaqSliding faqat idle chegarani suradi: 6.9-kunda faol bo'lsa ham qisqa sessiya 168-soatda (7 kun) tugaydi. Eslab qolingan sessiyada 2-kundagi faollik idle'ni 9-kungacha (216 soat) surdi.
3-mashq (qiyin): 60 soniyalik qadam
yozuvlarSoni(sorovlar, qadam) ni yozing: so'rov vaqtlari (ms, o'sib boradi) bo'yicha oxirgi_faollik necha marta bazaga yozilishini sanasin. Qoida kanondagidek: t - oxirgi > qadam bo'lsa yoziladi va oxirgi = t; boshida oxirgi = 0 (sessiya yaratilgan payt). 10 daqiqa davomida har 5 soniyada bitta so'rov uchun qadam = 0 (har so'rovda) va qadam = 60000 ni solishtiring, oxirida bazadagi faollik oxirgi so'rovdan necha soniya orqada qolganini chiqaring. Ishora: Array.from({ length: 120 }, (_, i) => …) (flat, flatMap, fill va Array.from) va at(-1).
Yechim
const QADAM = 60_000;
// so'rovlar vaqtlari (ms) bo'yicha bazaga necha marta yozilishini
// sanaydi; oxirgi — sessiya yaratilgan payt
function yozuvlarSoni(sorovlar, qadam) {
let oxirgi = 0;
let yozuv = 0;
for (const t of sorovlar) {
if (t - oxirgi > qadam) {
yozuv++;
oxirgi = t;
}
}
return { yozuv, oxirgi };
}
// 10 daqiqa davomida har 5 soniyada bitta so'rov
const sorovlar = Array.from(
{ length: 120 }, (_, i) => (i + 1) * 5000);
const har = yozuvlarSoni(sorovlar, 0);
const qadamli = yozuvlarSoni(sorovlar, QADAM);
console.log("so'rovlar:", sorovlar.length);
console.log("har so'rovda yozish:", har.yozuv);
console.log("60 s qadam bilan:", qadamli.yozuv);
const ortda = sorovlar.at(-1) - qadamli.oxirgi;
console.log("bazadagi faollik ortda qolgan:", ortda / 1000, "s");Konsolda:
so'rovlar: 120
har so'rovda yozish: 120
60 s qadam bilan: 9
bazadagi faollik ortda qolgan: 15 s120 o'rniga 9 ta yozuv — 13 marta kam. Narxi: bazadagi oxirgi_faollik 15 soniya orqada (umuman — 60 soniyagacha). 12 soatlik yoki 7 kunlik idle uchun bu sezilmaydi. Agar idle 5 daqiqa bo'lsa (bank), qadamni ham kichraytirish kerak bo'lardi.
10. Loyiha qadami: eslab qolish, qurilmalar va ADR
10.1 Migratsiya sql/113-sessiya-eslab-qol.sql
Izohlarni qisqartirdik, SQL kanondagidek:
-- "Meni eslab qol" va sessiyaga xos bo'sh turish chegarasi.
-- Mavjud qatorlar — eslab qolingan (oldingi xulq o'zgarmaydi).
-- DEFAULT bilan ADD COLUMN — PG 11+ da jadvalni qayta yozmaydi.
ALTER TABLE sessiyalar
ADD COLUMN eslab_qol boolean NOT NULL DEFAULT true;
ALTER TABLE sessiyalar
ADD COLUMN idle_muddat interval NOT NULL
DEFAULT interval '7 days';
ALTER TABLE sessiyalar ADD CONSTRAINT sessiyalar_idle_muddat_check
CHECK (idle_muddat BETWEEN interval '1 minute'
AND interval '400 days');Prisma tomoni: prisma/migrations/20261027130000_sessiya_eslab_qol/migration.sql (xuddi shu SQL) va schema.prisma dagi Sessiya modeliga ikki maydon. Prisma interval turini bilmaydi — u Unsupported bilan e'lon qilinadi (Prisma Migrate chuqur):
eslabQol Boolean @default(true) @map("eslab_qol")
idleMuddat Unsupported("interval")
@default(dbgenerated("'7 days'::interval")) @map("idle_muddat")Sxemani lokal bazada (110–113 migratsiyalari bilan) tekshirdik — biz ishga tushirdik, PostgreSQL 17.6:
SELECT column_name, column_default
FROM information_schema.columns
WHERE table_name = 'sessiyalar'
AND column_name IN ('eslab_qol', 'idle_muddat')
ORDER BY column_name;Natija:
column_name | column_default
-------------+--------------------
eslab_qol | true
idle_muddat | '7 days'::interval
(2 rows)Chegaradan tashqari qiymat:
\set VERBOSITY terse
INSERT INTO sessiyalar (foydalanuvchi_id, token_hash, tugash_vaqti,
idle_muddat)
VALUES (1, sha256('namuna'), now() + interval '1 day',
interval '2 years');Natija:
ERROR: new row for relation "sessiyalar" violates check constraint "sessiyalar_idle_muddat_check""Yangi qator sessiyalar_idle_muddat_check cheklovini buzadi". Kod xato qilib 2 yillik idle yozmoqchi bo'lsa, baza rad etadi.
10.2 Kanon topilmasi: 168:00:00
Kod idle'ni millisekundda beradi: $7 * interval '1 millisecond'. Testda kutilgan 7 days o'rniga boshqa narsa chiqdi:
SELECT (604800000 * interval '1 millisecond')::text AS ms_dan,
(interval '7 days')::text AS kunlik,
604800000 * interval '1 millisecond' = interval '7 days'
AS tengmi;Natija:
ms_dan | kunlik | tengmi
-----------+--------+--------
168:00:00 | 7 days | t
(1 row)Postgres interval ni uch qismda saqlaydi: oylar, kunlar va mikrosekundlar (SQL'da sana va vaqt zonalari). Millisekunddan yasalgan interval "kunlar" qismiga emas, vaqt qismiga tushadi — 168:00:00. Vaqtni hisoblashda ular teng (t), faqat matn ko'rinishi boshqa. Kanon testi shunga moslandi va izoh qo'shildi. Bitta tuzoq: yozgi vaqtga o'tish bor zonalarda 1 day va 24 hours sanaga qo'shilganda farq qilishi mumkin; bizning server UTC'da ishlaydi.
10.3 Kod
src/auth/sessiya.ts da o'zgargan joylar (to'liq fayl telefon uchun bo'lingan; uzun tiplarni alohida nom bilan chiqardik, ma'no kanondagidek — tip, lint va 18 test o'tdi, biz tekshirdik). Sozlama — ikki rejim:
const SOAT = 3600_000;
export const SUKUT: SessiyaSozlamasi = Object.freeze({
idleMs: 7 * 24 * SOAT,
muddatMs: 30 * 24 * SOAT,
qisqaIdleMs: 12 * SOAT,
qisqaMuddatMs: 7 * 24 * SOAT,
secure: true,
});yarat() — rejimga qarab muddat va idle tanlanadi va har sessiyaning o'z idle'i bazaga yoziladi:
async yarat(
foydalanuvchiId: number,
mijoz: Mijoz,
eslabQol = true,
): Promise<{ token: string; tugash: Date; eslabQol: boolean }> {
const token = randomBytes(32).toString("base64url");
const [muddat, idle] = eslabQol
? [this.s.muddatMs, this.s.idleMs]
: [this.s.qisqaMuddatMs, this.s.qisqaIdleMs];
const [q] = await this.#sorov<{ tugash: Date }>(
`INSERT INTO sessiyalar (foydalanuvchi_id, token_hash,
tugash_vaqti, ip, user_agent, eslab_qol, idle_muddat)
VALUES ($1, $2, now() + $3 * interval '1 millisecond', $4, $5,
$6, $7 * interval '1 millisecond')
RETURNING tugash_vaqti AS tugash`,
[
foydalanuvchiId,
tokenHash(token),
muddat,
mijoz.ip ?? null,
mijoz.userAgent?.slice(0, 300) ?? null,
eslabQol,
idle,
],
);
if (q === undefined) throw new Error("sessiya yaratilmadi");
return { token, tugash: q.tugash, eslabQol };
}tekshir() dagi WHERE endi umumiy sozlama emas, qatorning o'z chegarasini oladi: AND s.oxirgi_faollik > now() - s.idle_muddat. Keyin sozlamani o'zgartirsangiz, mavjud sessiyalar o'z qoidasi bilan yashashda davom etadi.
Cookie — eslab qolinmasa Expires siz:
cookieQoy(
res: Response,
token: string,
tugash: Date,
eslabQol = true,
): void {
const a = this.#atributlar();
res.cookie(
cookieNomi(this.s),
token,
eslabQol ? { ...a, expires: tugash } : a,
);
}Qo'shimcha metodlar: royxat(foydalanuvchiId, joriyId) — faol sessiyalar (id, joriy, eslabQol, yaratilgan, oxirgiFaollik, tugash, ip, qurilma), egasiBekorQilsin(foydalanuvchiId, sessiyaId) — yuqoridagi IDOR'siz UPDATE, qurilma(ua) — «Qurilmalar ro'yxati» bo'limidagi funksiya.
10.4 Yo'llar, sxema va config
src/auth/sxema.ts — ro'yxat va kirish sxemalariga bitta maydon: eslabQol: z.boolean().default(false). src/auth/marshrut.ts — sessiyaBer(req, res, f, body.eslabQol) va ikki yangi yo'l (bo'lingan):
// qurilmalar ro'yxati va bittasini bekor qilish
r.route("/sessiyalar")
.get(async (req, res) => {
if (req.sessiya === undefined) throw new KirilmaganXatosi();
const { foydalanuvchi, id } = req.sessiya;
res.json({
sessiyalar: await sessiyalar.royxat(foydalanuvchi.id, id),
});
})
.all(metodYoq("GET, HEAD"));
const IdSxemasi = z.strictObject({
id: z.coerce.number().int().positive()
.max(Number.MAX_SAFE_INTEGER),
});
r.route("/sessiyalar/:id")
.delete(tekshirib({ params: IdSxemasi },
async ({ params }, req, res) => {
if (req.sessiya === undefined) throw new KirilmaganXatosi();
const fid = req.sessiya.foydalanuvchi.id;
// Begona sessiya id — 404 (borligini oshkor qilmaymiz)
if (!(await sessiyalar.egasiBekorQilsin(fid, params.id))) {
throw new TopilmadiXatosi("Sessiya topilmadi",
"sessiya-topilmadi", "Sessiya topilmadi");
}
await xizmat.hodisa({ tur: "sessiya_bekor",
foydalanuvchiId: fid, ...mijoz(req),
tafsilot: { sessiyaId: params.id } });
if (params.id === req.sessiya.id) sessiyalar.cookieOchir(res);
res.sendStatus(204);
}))
.all(metodYoq("DELETE"));O'z sessiyasini o'chirsa — cookie ham o'chiriladi (cookieOchir). Jurnalga yangi hodisa turi — sessiya_bekor. GET /v1/auth/men endi sessiya.id va eslabQol ni ham qaytaradi. Config'ga ikki o'zgaruvchi: SESSIYA_QISQA_IDLE_DAQIQA (sukut 720) va SESSIYA_QISQA_MUDDAT_KUN (sukut 7).
Kanon tekshiruvining jonli qismida login endi eslab qolinmagan cookie beradi — Expires yo'q:
Set-Cookie: __Host-sessiya=<token>; Path=/; HttpOnly; Secure; SameSite=Lax{"foydalanuvchi":{"id":1,"email":"aziz@example.com","toliqIsm":"Aziz Karimov"},"sessiya":{"id":1,"eslabQol":false,"yaratilgan":"2026-10-27T15:00:38.239Z","tugash":"2026-11-03T15:00:38.239Z"}}Ikkinchi qator — /men javobi: tugash yaratilgandan aniq 7 kun keyin.
10.5 Testlar
test/sessiya.test.ts ga ikkita test qo'shildi. Birinchisining yadrosi (bo'lingan):
const q = await kir(); // eslabQol berilmagan
assert.match(setCookie(q),
/^__Host-sessiya=[^;]+; Path=\/; HttpOnly; Secure; SameSite=Lax$/);
const u = await kir(true); // eslab qol
assert.match(setCookie(u), /Expires=/);
// 13 soat jimlik: qisqasi tugaydi, eslab qolingani tirik
await pool.query("UPDATE sessiyalar SET oxirgi_faollik = " +
"now() - interval '13 hours'");
await request(app).get("/v1/auth/men")
.set("Cookie", cookieniOl(q.headers["set-cookie"])).expect(401);
await request(app).get("/v1/auth/men")
.set("Cookie", cookieniOl(u.headers["set-cookie"])).expect(200);setCookie(r) — o'qish uchun qisqartirildi (kanonda Set-Cookie sarlavhasining birinchi qiymati to'g'ridan olinadi). Ikkinchi test: ikki qurilma, ro'yxatda 2 ta sessiya va joriysi "Chrome, Windows", javob matnida token yo'q. Dilnozaning sessiyasini o'chirish — 404 (Dilnoza tirik qoladi). O'z telefonini o'chirish — 204 (telefon 401, kompyuter 200); /sessiyalar/abc — 400.
Biz ishga tushirdik — lokal PostgreSQL 17.6 da (TEST_AUTH_DATABASE_URL — sxemasi qurilgan bo'sh baza), butun fayl, yangi ikki test qatori:
✔ meni eslab qol: belgilanmasa — Expires'siz cookie, idle 12 soat, mutlaq 7 kun; belgilansa — 30 kun / 7 kun (392.9703ms)
✔ qurilmalar: ro'yxat (joriy belgilangan, token yo'q), bittasini bekor qilish; begona sessiya — 404 (576.1921ms)
ℹ tests 18
ℹ pass 18
ℹ fail 0Test nomlari boshidagi kanon ichki raqamini olib tashladik. Chiqishdan boshqa 16 test qatori va qolgan xulosa qatorlarini tushirib qoldirdik.
10.6 Tekshiruv va commit
Kanon tekshiruvi (2026-10-27, PostgreSQL 18.6): tip va lint xatosiz, testlar 149 (148 + 1 o'tkazilgan) — ikkita yangi; e2e 4/4; Prisma migratsiyalari va schema.prisma orasida farq yo'q.
M="feat(auth): \"meni eslab qol\", sessiyaga xos idle, qurilmalar"
M+=" ro'yxati va bitta sessiyani bekor qilish;"
M+=" docs: ADR — brauzer uchun server sessiyasi; db: 113"
git add src test sql prisma docs/AUTH-QAROR.md
git commit -m "$M"11. Real ishda
- ADR ko'p jamoada
docs/adr/0001-….mdko'rinishida raqamlanadi va kod bilan birga review qilinadi. Qaror o'zgarsa, eski ADR o'chirilmaydi — "o'rniga 0007 keldi" deb belgilanadi. - Faol sessiyalar sahifasi (Telegram, Google, banklar) — aynan bugungi
royxatvaegasiBekorQilsin. Ko'p xizmat yangi qurilmadan kirilganda xat ham yuboradi. - Express dunyosida
express-sessionningrolling: truesozlamasi — sliding expiration'ning o'zi. Biz uni o'z kodimizda, 60 s qadam bilan yozdik. - Intervyuda: "Session yoki JWT — qachon qaysi?", "JWT'ni qanday bekor qilasiz?", "Idle va absolute timeout farqi?", "Remember me qanday xavfsiz qilinadi?".
Xulosa
- Bir saytdagi brauzer uchun server sessiyasi soddaroq, darhol bekor qilinadi va kichik; JWT 1.4 ms tez, lekin bekor qilinmaydi.
- Qaror o'lchovlar bilan ADR'ga yoziladi; eng qimmat bo'lim — "qachon qayta ko'riladi".
- Uzoq sessiya — ikki chegara: idle (sliding) va mutlaq; ikkalasi ham serverda.
- "Meni eslab qol" sukut bo'yicha o'chiq; eslab qolinmagan cookie
Expiressiz, lekin chegara baribir bazada. - Qurilmalar ro'yxatida token yo'q, User-Agent — faqat ko'rsatish uchun; bitta sessiyani o'chirishda egasi sharti SQL'da, begonaga — 404.
- Postgres
intervalmillisekunddan168:00:00bo'ladi —7 daysga teng, faqat yozilishi boshqa.
Keyingi dars: Email tasdiqlash va parol tiklash oqimlari — bir martalik tokenlar, email borligini oshkor qilmaydigan javob va parol tiklangandan keyin hamma sessiyalarni bekor qilish.
Manbalar
- OWASP Session Management Cheat Sheet (idle va absolute timeout): https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html (2026-10-28 da o'qildi)
- Michael Nygard, "Documenting Architecture Decisions" (2011): https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions
- RFC 8252 — OAuth 2.0 for Native Apps: https://www.rfc-editor.org/rfc/rfc8252
- PostgreSQL 18: Date/Time Types (
intervalichki tuzilishi): https://www.postgresql.org/docs/18/datatype-datetime.html - Kanon o'lchovi (IlmHamroh, 2026-10-27): sessiya store'lari tajribasi (express-session 1.19.0, connect-pg-simple 10.0.0, connect-redis 10.0.0), refresh rotatsiyasi tajribasi (jose 6.2.12), holat 11 tekshiruvi va
docs/AUTH-QAROR.md. Darsdagi brauzer misollari, SQL (PostgreSQL 17.6) vasessiya.test.ts— biz ishga tushirdik (2026-10-28).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!