IlmHamroh
JavaScript Full-stack/28-qism. Autentifikatsiya va backend xavfsizligi6/31-dars20 daqiqa
Mundarija (23)

Cookie chuqur: HttpOnly, Secure, SameSite va __Host- prefiksi

Qisqacha: Cookie — brauzer saqlaydigan va har so'rovga o'zi qo'shadigan kichik yozuv. Uning xavfsizligi atributlarda: HttpOnly — JavaScript o'qiy olmaydi, Secure — faqat HTTPS orqali, SameSite — boshqa saytdan kelgan so'rovga qo'shilmaydi, __Host- prefiksi — subdomen soxtalashtira olmaydi. Kanon 14 ta cookie'ni haqiqiy Chromium'da sinadi: 10 tasi saqlandi, 4 tasini brauzer rad etdi. Eng kutilmagan natija: SameSite bir xil host'ning boshqa portidan kelgan hujumni to'xtatmaydi — port "sayt" tushunchasiga kirmaydi.

Bu darsda

  • Set-Cookie sarlavhasini o'qiysiz va har atributning vazifasini tushuntirasiz.
  • "Sayt" va "origin" farqini bilib, SameSite qachon himoya qilishini aytasiz.
  • Brauzer qaysi cookie'ni rad etishini oldindan aytib bera olasiz (__Host-, SameSite=None, 4096 bayt).
  • Imzolangan cookie'ni Web Crypto HMAC bilan yasaysiz va tekshirasiz.
  • vazifalar-api ga cookieOl() yordamchisini qo'shasiz.

Oldin bilishingiz kerak: HTTP sarlavhalari, MIME turlari, cookie va kesh, Cookie'lar JS'dan, res obyekti, crypto 1: tasodif, hash va HMAC, Ro'yxat va login oqimi.

Oldingi darsda login "ha, bu Aziz" deyishni o'rgandi. Lekin HTTP xotirasiz: keyingi so'rovda server Azizni tanimaydi. Brauzer har so'rov bilan "men — Aziz" degan isbotni olib kelishi kerak. Bu isbot odatda cookie ichida yuradi. Shuning uchun sessiyani qurishdan oldin cookie'ni chuqur bilishimiz kerak.

1. Nega bu kerak?

Cookie'ni 2- va 11-qismlarda ko'rgansiz: server Set-Cookie yuboradi, brauzer saqlaydi, keyingi so'rovlarga Cookie sarlavhasi bilan qo'shadi. document.cookie bilan JS ham o'qiy oladi.

Auth uchun bu "o'zi qo'shiladi" xususiyati ham kuch, ham xavf:

  • Kuch: frontend tokenni saqlash va har fetch ga qo'shish haqida o'ylamaydi.
  • Xavf: brauzer cookie'ni boshqa sayt boshlagan so'rovga ham qo'shishi mumkin. Hujumchi saytidagi forma sizning API'ingizga POST yuboradi — brauzer esa Azizning cookie'sini "sadoqat bilan" qo'shib beradi. Bu CSRF (Cross-Site Request Forgery) hujumining asosi.

Ikkala tomon bitta diagrammada. Uchinchi strelkaga qarang: hujumchi sahifasi boshlagan so'rovga ham brauzer cookie'ni o'zi qo'shmoqchi bo'ladi.

sequenceDiagram
  participant B as Brauzer
  participant A as vazifalar-api
  participant H as Hujumchi sahifasi
  A-->>B: Set-Cookie: sessiya=...
  B->>A: GET /v1/vazifalar + Cookie
  H->>B: yashirin forma POST
  B->>A: POST /v1/vazifalar + Cookie?

So'roq belgisi bejiz emas: cookie qo'shiladimi yo'qmi — atributlarga bog'liq. Atributlar aynan shu ikki tomonni boshqaradi. Noto'g'ri bitta atribut — va sessiya XSS bilan o'g'irlanadi yoki boshqa saytdan ishlatiladi.

2.1 Simdagi ko'rinish

Server cookie'ni javob sarlavhasi bilan beradi. Har cookie — alohida Set-Cookie qatori:

js
import { createServer } from "node:http";

const server = createServer((req, res) => {
  res.setHeader("Set-Cookie", [
    "sessiya=abc123; Path=/; HttpOnly; Secure; SameSite=Lax",
    "til=uz; Path=/; Max-Age=31536000",
  ]);
  res.end("ok");
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address();

const javob = await fetch(`http://127.0.0.1:${port}/`);
for (const c of javob.headers.getSetCookie()) console.log(c);
console.log(javob.headers.get("set-cookie"));

server.closeAllConnections();
server.close();

Konsolda:

text
sessiya=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
til=uz; Path=/; Max-Age=31536000
sessiya=abc123; Path=/; HttpOnly; Secure; SameSite=Lax, til=uz; Path=/; Max-Age=31536000

Birinchi ikki qator — getSetCookie(): har cookie alohida. Uchinchisi — oddiy get(): ikkalasini vergul bilan qo'shib yuboradi. Bu xavfli, chunki Expires=Thu, 26 Nov 2026 … sanasida ham vergul bor — bunday satrni to'g'ri bo'lib bo'lmaydi. Shuning uchun Set-Cookie uchun maxsus getSetCookie() metodi bor. Bu blok faqat Node'da ishlaydi — saytda «Ishga tushir» tugmasi chiqmaydi, o'z terminalingizda yurgizing.

2.2 Atributlar xaritasi

nom=qiymat dan keyin ; bilan atributlar keladi. Brauzer ularni o'zida saqlaydi va serverga qaytarmaydi: Cookie sarlavhasida faqat nom=qiymat boradi.

Atribut Nima qiladi Xavfsizlikdagi o'rni
HttpOnly document.cookie da ko'rinmaydi XSS skripti cookie'ni o'g'irlay olmaydi
Secure faqat HTTPS orqali yuboriladi tarmoqda ochiq yurmaydi
SameSite boshqa saytdan boshlangan so'rovga qo'shiladimi CSRF'ga qarshi birinchi qatlam
Domain qaysi host(lar)ga yuboriladi qo'yilsa — subdomenlarga ham ketadi
Path qaysi yo'llarga yuboriladi xavfsizlik chegarasi emas
Max-Age / Expires qachon o'chadi yo'q bo'lsa — brauzer yopilganda

Max-Age soniyada, Expires — aniq sana. Ikkalasi bo'lsa, Max-Age ustun. Hech biri bo'lmasa — sessiya cookie'si: brauzer yopilganda o'chadi (lekin "oldingi oynalarni tiklash" yoqilgan brauzer uni saqlab qolishi mumkin).

3.1 Qaysilari saqlandi

Kanon node:http serverini ko'tarib, bitta javobda 14 ta Set-Cookie yubordi va Chromium nimani saqlaganini o'qidi. Brauzer — Playwright o'rnatgan Chromium headless shell 151.0.7922.34, server http://localhost:4340, TLS yo'q. Natijadan (o'lchov jurnalidan aynan):

text
=== 1) http://localhost:4340/qoy — 14 ta Set-Cookie dan brauzer saqlagani (10):
  __Host-sid[S][H][Lax]  __Secure-s[S][Lax]  api_yol[Lax]  httponly[H][Lax]  lax[Lax]  none[S][None]  qisqa[Lax]  secure[S][Lax]  strict[Strict]  sukut[Lax]
  rad etilganlar: __Host-secsiz, __Host-domen, none_secsiz, katta
  document.cookie (HttpOnly ko'rinmaydi): __Secure-s,lax,none,qisqa,secure,strict,sukut
  1.2 s dan keyin qisqa (Max-Age=1): o'chdi
  /korsat (oz-sayti) yuborildi: __Host-sid,__Secure-s,httponly,lax,none,secure,strict,sukut
  /api/korsat (oz-sayti-api) yuborildi: __Host-sid,__Secure-s,api_yol,httponly,lax,none,secure,strict,sukut

[S] — Secure, [H] — HttpOnly, qavsdagi so'z — SameSite. Qatorma-qator o'qiymiz:

  1. sukut[Lax] — SameSite yozilmagan cookie'ga Chromium o'zi Lax qo'ydi. Bu "Lax-by-default" xulqi.
  2. Rad etilgan to'rtta — keyingi bo'limda sababi bilan.
  3. document.cookie da __Host-sid va httponly yo'q — HttpOnly ishladi. api_yol ham yo'q: u Path=/api bilan, sahifa esa /qoy da.
  4. Max-Age=1 — 1.2 soniyadan keyin o'chdi.
  5. /korsat ga api_yol bormadi, /api/korsat ga bordi — Path ishladi. qisqa ikkalasida ham yo'q — u allaqachon o'chgan edi.

Eng muhim kuzatuv sarlavhada yashiringan: server http da, TLS yo'q. Shunga qaramay Secure va __Host- cookie'lar saqlandi. Kanon xuddi shu sinovni http://127.0.0.1:4340 da ham qildi — natija aynan bir xil. Chromium localhost va 127.0.0.1 ni "ishonchli" origin deb biladi va ularda Secure ni HTTPS'siz ham qabul qiladi. Bu dasturchi uchun qulay: lokal kompyuterda ham prodakshndagi atributlar bilan ishlash mumkin. LAN IP (192.168.…) yoki oddiy http domen esa bunday imtiyozga ega emas.

3.2 Brauzer nimani rad etadi

Rad etish qoidalarini kodga aylantiramiz va kanon ro'yxatining bir qismida sinaymiz:

js
function tahlil(sarlavha) {
  const [juft, ...qolgan] = sarlavha.split(";").map((s) => s.trim());
  const nom = juft.slice(0, juft.indexOf("="));
  const atr = new Map();
  for (const q of qolgan) {
    const [k, v = ""] = q.split("=");
    atr.set(k.toLowerCase(), v);
  }
  return { nom, atr, hajm: new TextEncoder().encode(juft).length };
}

function radSababi({ nom, atr, hajm }) {
  if (hajm > 4096) return "4096 baytdan katta";
  const ss = atr.get("samesite")?.toLowerCase();
  if (ss === "none" && !atr.has("secure")) return "None — Secure'siz";
  if (nom.startsWith("__Secure-") && !atr.has("secure")) {
    return "__Secure- — Secure'siz";
  }
  if (nom.startsWith("__Host-")) {
    if (!atr.has("secure")) return "__Host- — Secure'siz";
    if (atr.has("domain")) return "__Host- — Domain bilan";
    if (atr.get("path") !== "/") return "__Host- — Path=/ emas";
  }
  return null;
}

const QOY = [
  "__Host-sid=1; Path=/; Secure; HttpOnly; SameSite=Lax",
  "__Host-secsiz=1; Path=/; HttpOnly",
  "__Host-domen=1; Path=/; Secure; Domain=localhost",
  "none=1; Path=/; SameSite=None; Secure",
  "none_secsiz=1; Path=/; SameSite=None",
  "sukut=1; Path=/",
  `katta=${"x".repeat(5000)}; Path=/`,
];
for (const s of QOY) {
  const c = tahlil(s);
  console.log(c.nom.padEnd(14), radSababi(c) ?? "saqlanadi");
}

Konsolda:

text
__Host-sid saqlanadi
__Host-secsiz __Host- — Secure'siz
__Host-domen __Host- — Domain bilan
none saqlanadi
none_secsiz None — Secure'siz
sukut saqlanadi
katta 4096 baytdan katta

Bizning kichik funksiyamiz haqiqiy Chromium bilan bir xil to'rtta cookie'ni rad etdi. Bu qoidalarning har biri bejiz emas:

  • __Host- prefiksi — brauzerga va'da: "bu cookie faqat shu host'dan, Secure bilan, Path=/ va Domainsiz qo'yilgan". Nega kerak? Tasavvur qiling, ilova.example.com ning sessiya cookie'si bor. Hujumchi blog.example.com subdomenini buzdi. Oddiy cookie bo'lsa, u Domain=example.com bilan o'z sessiya cookie'sini qo'yib, qurbonnikini "soya"da qoldira oladi. __Host- nomli cookie'ni esa Domain bilan qo'yib bo'lmaydi — brauzer rad etadi.
  • __Secure- — yumshoqroq: faqat Secure talab qiladi.
  • SameSite=None faqat Secure bilan — boshqa saytlarga ochiq cookie hech bo'lmaganda shifrlangan kanalda yursin.
  • 4096 bayt — RFC 6265 brauzerdan kamida shuncha hajmni qo'llashni talab qiladi; Chromium undan kattasini saqlamadi.

Tekshirib ko'ring: Nega __Host- cookie'da Path=/ majburiy? Path=/admin bilan qo'yilsa nima yomon?

Javob

Path — xavfsizlik chegarasi emas, faqat "qaysi yo'llarga yuboraman" degan filtr. Bir host'dagi /blog sahifasining skripti /admin cookie'sini ham yashirin iframe orqali o'qishi mumkin. __Host- "butun host uchun bitta cookie" g'oyasiga asoslangan: Path=/ bo'lsa, boshqa yo'lda xuddi shu nomli ikkinchi cookie yaratib, birinchisini chalkashtirib bo'lmaydi.

4. SameSite: sayt va origin

4.1 Ikki tushuncha

  • Origin — sxema + host + port: http://localhost:5173 va http://localhost:3000 — ikki xil origin. CORS shu bilan ishlaydi.
  • Sayt (site) — sxema + "ro'yxatdan o'tgan domen" (eTLD+1): example.com, ilmhamroh.uz. Port kirmaydi, subdomen ham kirmaydi. localhost:5173 va localhost:3000 — bitta sayt.

SameSite origin'ga emas, saytga qaraydi. "Boshqa saytdan" degani — so'rovni boshqa ro'yxatdan o'tgan domendagi sahifa boshladi.

Qiymat Boshqa saytdan boshlangan so'rovga qo'shiladimi
Strict hech qachon (havola bosilsa ham)
Lax faqat yuqori darajadagi xavfsiz navigatsiyada (havola, GET)
None har doim (Secure majburiy)

4.2 Kanon o'lchovi: beshta yo'l

Kanon cookie'larni localhost:4340 da qo'ydi va ikki "hujum" sahifasini ochdi: boshqa saytdagi (127.0.0.1:4340 — host boshqa) va bir saytning boshqa portidagi (localhost:4341). Har sahifa nishonga besh xil yo'l bilan so'rov yubordi: fetch (credentials: "include"), forma POST, iframe, <img> va yuqori darajadagi havola. Natija (o'lchov jurnalidan):

text
--- boshqa sayt (127.0.0.1):
  fetch-include  → none
  forma-post     → none
  havola-GET     → __Host-sid,__Secure-s,httponly,lax,none,secure,sukut
  iframe         → none
  rasm           → none
--- bir sayt, boshqa port (localhost):
  fetch-include  → __Host-sid,__Secure-s,httponly,lax,none,qisqa,secure,strict,sukut
  forma-post     → __Host-sid,__Secure-s,httponly,lax,none,qisqa,secure,strict,sukut
  havola-GET     → __Host-sid,__Secure-s,httponly,lax,none,secure,strict,sukut
  iframe         → __Host-sid,__Secure-s,httponly,lax,none,qisqa,secure,strict,sukut
  rasm           → __Host-sid,__Secure-s,httponly,lax,none,qisqa,secure,strict,sukut

Bu yerda none — cookie nomi (SameSite=None bilan qo'yilgan), "hech narsa" emas. Ikki xulosa:

  1. Boshqa saytdan fetch, forma, iframe, rasm — faqat none cookie'si bordi. Lax va sukutdagilar faqat havola bosilganda qo'shildi; strict hatto havolada ham yo'q. SameSite aytilgandek ishladi.
  2. Bir saytning boshqa portidan — hamma cookie, strict ham, hamma yo'l bilan bordi. Port sayt tushunchasiga kirmaydi.

Ikkinchi xulosa amaliy jihatdan juda muhim. Bitta kompyuterda yoki bitta domen ostida boshqa port yoki subdomenda ishlaydigan har qanday sahifa (masalan, buzilgan boshqa servis) uchun sizning SameSite=Strict cookie'ingiz himoya emas. Shuning uchun SameSite — CSRF'ga qarshi birinchi qatlam, yagonasi emas. Qo'shimcha qatlamlarni Tokenni qayerda saqlash va CSRF darsida quramiz.

flowchart TD
  S[So'rov: cookie qo'shilsinmi?] --> D{Domain, Path mos, muddati o'tmagan?}
  D -->|yo'q| N[Qo'shilmaydi]
  D --> X{Secure bo'lsa: HTTPS yoki localhost?}
  X -->|yo'q| N
  X --> B{So'rovni boshlagan sahifa bir saytdami?}
  B -->|ha| Y[Qo'shiladi]
  B -->|yo'q| M{SameSite}
  M -->|None| Y
  M -->|Strict| N
  M -->|Lax yoki yozilmagan| L{Yuqori darajadagi GET navigatsiyami?}
  L -->|ha| Y
  L -->|yo'q| N

Diagramma kanon kuzatuvlarini umumlashtiradi. "Bir saytdami?" savoli portni hisobga olmaydi — yuqoridagi ikkinchi xulosa shu tugundan chiqadi.

Tekshirib ko'ring: Frontend http://localhost:5173 da (Vite), API http://localhost:3000 da. API cookie'si SameSite=Strict. Frontend'dagi fetch(…, { credentials: "include" }) cookie'ni yuboradimi?

Javob

Ha. Ikkalasi bitta sayt (localhost), port saytga kirmaydi — kanon o'lchovida "bir sayt, boshqa port" holatida strict ham yuborildi. Lekin origin'lar har xil, shuning uchun brauzer javobni JS'ga faqat CORS ruxsat bersa beradi (Access-Control-Allow-Credentials: true va aniq origin). Cookie va CORS — ikki alohida mexanizm.

Ba'zan cookie'da sir bo'lmagan, lekin o'zgartirilmasligi kerak bo'lgan qiymat saqlanadi: tanlangan til, "bannerni yopdi" belgisi. Foydalanuvchi uni brauzerda bemalol o'zgartira oladi. Server o'zgartirilganini bilishi uchun qiymatga imzo qo'shiladi — HMAC (crypto 1):

js
const enc = new TextEncoder();
const b64url = (buf) =>
  btoa(String.fromCharCode(...new Uint8Array(buf)))
    .replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
const baytga = (s) => Uint8Array.from(
  atob(s.replaceAll("-", "+").replaceAll("_", "/")),
  (c) => c.charCodeAt(0));

// faqat namoyish uchun — haqiqiy sir .env da, 32 bayt tasodifiy
const kalit = await crypto.subtle.importKey(
  "raw", enc.encode("namuna-sir-faqat-dars-uchun-0001"),
  { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"],
);

async function imzola(qiymat) {
  const s = await crypto.subtle.sign(
    "HMAC", kalit, enc.encode(qiymat));
  return `${qiymat}.${b64url(s)}`;
}

async function tekshir(cookie) {
  const i = cookie.lastIndexOf(".");
  const [qiymat, imzo] = [cookie.slice(0, i), cookie.slice(i + 1)];
  const togri = await crypto.subtle.verify(
    "HMAC", kalit, baytga(imzo), enc.encode(qiymat));
  return togri ? qiymat : null;
}

const c = await imzola("til=uz");
console.log(c);
console.log(await tekshir(c));
console.log(await tekshir(c.replace("til=uz", "til=ru")));

Konsolda:

text
til=uz.DP1IG2i7EiKZhzd9AOhIgXtNbN-X9QWKAlP-kPVcje0
til=uz
null

Qiymat ochiq ko'rinadi — imzo uni yashirmaydi, faqat o'zgarishini sezadi. til=ru ga almashtirilgan cookie null qaytardi: imzo endi mos emas. Tekshiruvda === emas, crypto.subtle.verify ishlatdik — u solishtirishni o'zi bajaradi va natijani faqat true/false bilan beradi.

Express olamida buni cookie-parser paketi qiladi (res.cookie(…, { signed: true }) → s: prefiksli qiymat). Keyingi darsda express-session ham sessiya ID'sini xuddi shunday imzolashini ko'rasiz. Lekin bitta muhim cheklov bor: imzo qiymatni sir qilmaydi. Cookie ichida parol, token yoki shaxsiy ma'lumot bo'lsa — imzo yordam bermaydi; u holda cookie'da faqat tasodifiy ID, ma'lumot esa serverda turishi kerak.

Birinchi tomon (first-party) cookie — siz ochgan saytning o'zi qo'ygan. Uchinchi tomon (third-party) — sahifaga joylashtirilgan boshqa sayt (reklama, vidjet, iframe) qo'ygan. Uchinchi tomon cookie'lari ko'p yillar foydalanuvchini saytdan saytga kuzatish uchun ishlatildi. Shuning uchun brauzerlar ularni cheklay boshladi (hujjatlar bo'yicha, 2026-10-28 holatiga):

Brauzer Uchinchi tomon cookie'lari
Safari (ITP) sukut bo'yicha bloklangan
Firefox (Total Cookie Protection) har sayt uchun alohida "quti"ga ajratilgan
Chrome sukut bo'yicha ruxsat; Google 2024–2025 yillarda ularni o'chirish rejasidan voz kechdi va Privacy Sandbox loyihasining katta qismini to'xtatdi

Kanon headless shell'da SameSite=None cookie'si boshqa saytdan yuborildi — Chromium'da bloklanmagan. Safari va Firefox sinalmagan.

Joylashtirilgan vidjetga (masalan, boshqa saytlarga qo'yiladigan chat oynasi) cookie kerak bo'lsa, zamonaviy yechim — CHIPS (Cookies Having Independent Partitioned State): Partitioned atributi. Bunday cookie har yuqori darajadagi sayt uchun alohida saqlanadi — vidjet a.uz va b.uz da ikki xil cookie oladi va foydalanuvchini saytlar orasida kuzata olmaydi. Kursdagi loyihada bunday ehtiyoj yo'q, kanon CHIPS'ni sinamagan.

vazifalar uchun xulosa oddiy: frontend va API bitta saytda (dev'da Vite proksi, prodakshnda bitta domen) — demak bizning sessiya cookie'miz birinchi tomon cookie'si va bu cheklovlar unga tegmaydi.

7. Hujumchi nigohi

Har hujum yonida himoya va uni tasdiqlagan o'lchov yoki test:

Hujum Himoya Tekshiruv (bu darsda)
XSS document.cookie ni o'qiydi HttpOnly document.cookie da __Host-sid yo'q
ochiq Wi-Fi'da ushlash Secure + HTTPS Secure cookie faqat HTTPS (va localhost)
boshqa saytdan forma POST SameSite=Lax/Strict boshqa saytdan faqat none bordi
bir saytning boshqa portidan Origin tekshiruvi (CSRF darsi) strict ham bordi — SameSite yetmaydi
subdomen Domain= bilan qo'yadi __Host- prefiksi __Host-domen rad etildi
til cookie'sini almashtirish imzo (HMAC) til=ru → null
imzoga sir yashirish cookie'da faqat ID imzoli qiymat ochiq ko'rinadi

Nega bu hujumlar ishlaydi — yuqorida ko'rdik: cookie o'zi qo'shiladi, SameSite portni ko'rmaydi, imzo shifrlamaydi.

8. Ko'p uchraydigan xatolar

Xato Nega xavfli To'g'risi
sessiya cookie'si HttpOnly siz bitta XSS — sessiya o'g'irlandi HttpOnly
SameSite=None "har ehtimolga qarshi" CSRF eshigi ochiq Lax, kerak bo'lsa Strict
Domain=example.com "qulaylik uchun" hamma subdomenga ketadi Domain yozmang, __Host-
Path ni xavfsizlik deb bilish bir host ichida chegara emas alohida host yoki origin
headers.get("set-cookie") bilan bo'lish Expires dagi vergul getSetCookie()
cookie'da JSON ko'rinishida profil 4096 bayt, ochiq, o'zgartiriladi serverda saqlash, cookie'da ID
"SameSite bor — CSRF yo'q" boshqa port bir sayt Origin tekshiruvi ham

9. Mashqlar

Mashqlar kurs/mashqlar/28/06-cookie/ da.

1-mashq (oson): to'g'ri yoki noto'g'ri

  1. HttpOnly cookie serverga yuborilmaydi, faqat brauzerda saqlanadi.
  2. localhost:5173 va localhost:3000 — bitta sayt.
  3. SameSite=Strict cookie boshqa saytdagi havola bosilganda yuborilmaydi.
  4. Chromium'da Secure cookie http://localhost da qabul qilinadi.
  5. Imzolangan cookie'ning qiymatini foydalanuvchi o'qiy olmaydi.
Yechim
  1. Noto'g'ri — HttpOnly faqat JS'dan yashiradi; cookie har so'rov bilan serverga boradi.
  2. To'g'ri — port saytga kirmaydi; kanon o'lchovida "bir sayt, boshqa port" holatida hamma cookie bordi.
  3. To'g'ri — kanon: boshqa saytdan havola-GET'da lax va sukut bordi, strict yo'q.
  4. To'g'ri — localhost va 127.0.0.1 ishonchli origin; kanon ikkalasida ham sinadi.
  5. Noto'g'ri — imzo faqat o'zgarishni sezadi; qiymat ochiq (til=uz.…).

setCookie(nom, qiymat, o) funksiyasini yozing. U nom=qiymat (qiymat — encodeURIComponent bilan), Path (sukut /), so'ng mavjud bo'lsa Max-Age, Domain, HttpOnly, Secure, SameSite ni ; bilan qo'shadi. Uch holatda xato tashlasin: nomda harf, raqam, _, - dan boshqa belgi bor; __Host- nomi Securesiz, Domain bilan yoki Path / emas; SameSite=None Securesiz. Ikkita to'g'ri va uchta xato misolda sinang. Ishora: xatoni ushlash — try/catch (try / catch / finally).

Yechim
js
function setCookie(nom, qiymat, o = {}) {
  if (!/^[\w-]+$/.test(nom)) throw new Error(`nom yaroqsiz: ${nom}`);
  const q = [`${nom}=${encodeURIComponent(qiymat)}`];
  q.push(`Path=${o.path ?? "/"}`);
  if (o.maxAge !== undefined) q.push(`Max-Age=${o.maxAge}`);
  if (o.domain) q.push(`Domain=${o.domain}`);
  if (o.httpOnly) q.push("HttpOnly");
  if (o.secure) q.push("Secure");
  if (o.sameSite) q.push(`SameSite=${o.sameSite}`);
  const s = q.join("; ");
  if (nom.startsWith("__Host-") && (!o.secure || o.domain ||
      (o.path ?? "/") !== "/")) {
    throw new Error("__Host-: Secure, Path=/ va Domain'siz bo'lsin");
  }
  if (o.sameSite === "None" && !o.secure) {
    throw new Error("SameSite=None faqat Secure bilan");
  }
  return s;
}

console.log(setCookie("__Host-sessiya", "a b", {
  httpOnly: true, secure: true, sameSite: "Lax", maxAge: 2592000,
}));
console.log(setCookie("til", "o'zbek"));
for (const [nom, o] of [
  ["__Host-x", { secure: true, domain: "example.com" }],
  ["t", { sameSite: "None" }],
  ["yomon nom", {}],
]) {
  try {
    setCookie(nom, "1", o);
  } catch (x) {
    console.log("xato:", x.message);
  }
}

Konsolda:

text
__Host-sessiya=a%20b; Path=/; Max-Age=2592000; HttpOnly; Secure; SameSite=Lax
til=o'zbek; Path=/
xato: __Host-: Secure, Path=/ va Domain'siz bo'lsin
xato: SameSite=None faqat Secure bilan
xato: nom yaroqsiz: yomon nom

Bo'shliq %20 ga aylandi — cookie qiymatida bo'shliq, ; va , bo'lmasligi kerak. o'zbek dagi apostrof encodeURIComponent da o'zgarmaydi — u cookie qiymatida ruxsat etilgan belgi. Brauzer rad etadigan cookie'ni serverning o'zi yaratmasligi yaxshiroq: xato jim yo'qolib ketmaydi.

3-mashq (qiyin): SameSite simulyatori

yuboriladimi(sameSite, s) funksiyasini yozing: s — { tur, metod, saytlararo }, tur — fetch, forma, havola yoki rasm. Bir saytdan bo'lsa — har doim true; boshqa saytdan: None — true, Strict — false, Lax — faqat havola va xavfsiz metod (GET, HEAD). Beshta so'rov uchun qaysi SameSite qiymatlari cookie'ni yuborishini chiqaring va natijani kanon o'lchovi bilan solishtiring. Ishora: filter va join (filter: shart bo'yicha tanlash).

Yechim
js
const XAVFSIZ = new Set(["GET", "HEAD"]);

function yuboriladimi(sameSite, s) {
  if (!s.saytlararo) return true; // bir sayt — hamma rejim yuboradi
  if (sameSite === "None") return true;
  if (sameSite === "Strict") return false;
  // Lax (va SameSite yozilmagan) — faqat yuqori darajadagi xavfsiz
  // navigatsiyada
  return s.tur === "havola" && XAVFSIZ.has(s.metod);
}

const SOROVLAR = [
  { tur: "fetch", metod: "GET", saytlararo: true },
  { tur: "forma", metod: "POST", saytlararo: true },
  { tur: "havola", metod: "GET", saytlararo: true },
  { tur: "rasm", metod: "GET", saytlararo: true },
  { tur: "forma", metod: "POST", saytlararo: false },
];
for (const s of SOROVLAR) {
  const natija = ["Strict", "Lax", "None"]
    .filter((ss) => yuboriladimi(ss, s)).join(",");
  const joy = s.saytlararo ? "boshqa sayt" : "bir sayt";
  console.log(`${s.tur} ${s.metod} (${joy}):`, natija);
}

Konsolda:

text
fetch GET (boshqa sayt): None
forma POST (boshqa sayt): None
havola GET (boshqa sayt): Lax,None
rasm GET (boshqa sayt): None
forma POST (bir sayt): Strict,Lax,None

Natija kanon o'lchovidagi jadval bilan mos: boshqa saytdan fetch, forma va rasmda faqat none, havolada lax va none ham, bir saytda — hammasi. Simulyator qoidani tushunish uchun; haqiqiy brauzerda yana nozik joylar bor (iframe ichidagi navigatsiya, yo'naltirishlar zanjiri), shuning uchun muhim qarorni doim haqiqiy brauzerda sinang — kanon Playwright bilan aynan shunday qildi.

Bu darsning loyihadagi izi kichik, lekin keyingi dars uchun poydevor. vazifalar-api keyingi darsda sessiya cookie'sini qo'yadi va o'qiydi. O'qish uchun cookie-parser paketini qo'shish mumkin edi. Kanon boshqacha qaror qildi: bizga bitta cookie kerak, uni ajratib olish — o'n qatorli funksiya. Har yangi bog'liqlik — yangi xavf (Ta'minot zanjiri xavfsizligi).

Yangi fayl src/auth/sessiya.ts ning birinchi funksiyasi (keyingi darsda shu faylga sessiya ombori qo'shiladi):

ts
/** Cookie sarlavhasidan bitta cookie (birinchisi). Express'da
 * req.cookies faqat cookie-parser bilan — bitta nom uchun paket
 * kerak emas */
export function cookieOl(
  sarlavha: string | undefined,
  nom: string,
): string | undefined {
  if (sarlavha === undefined) return undefined;
  for (const qism of sarlavha.split(";")) {
    const i = qism.indexOf("=");
    if (i !== -1 && qism.slice(0, i).trim() === nom) {
      return qism.slice(i + 1).trim();
    }
  }
  return undefined;
}

Kanon kodini telefon uchun bo'ldik. Uni brauzerda ham sinab ko'ring (turlarsiz nusxa):

js
function cookieOl(sarlavha, nom) {
  if (sarlavha === undefined) return undefined;
  for (const qism of sarlavha.split(";")) {
    const i = qism.indexOf("=");
    if (i !== -1 && qism.slice(0, i).trim() === nom) {
      return qism.slice(i + 1).trim();
    }
  }
  return undefined;
}

const N = "__Host-sessiya";
console.log(cookieOl("a=1; __Host-sessiya=abc; b=2", N));
console.log(cookieOl("x__Host-sessiya=zz; __Host-sessiya=ok", N));
console.log(cookieOl("a=1", N));
console.log(cookieOl("__Host-sessiya=s=1=2", N));

Konsolda:

text
abc
ok
undefined
s=1=2

Ikkinchi qator — nozik joy: x__Host-sessiya ham "__Host-sessiya" bilan tugaydi. includes yoki endsWith bilan qidirgan sodda kod uning qiymatini (zz) qaytarardi. Kanon nomni = gacha kesib, aniq solishtiradi. To'rtinchi qator: qiymat ichidagi = saqlanadi — faqat birinchi = ajratuvchi.

Kanon testi (test/sessiya.test.ts ning birinchi testi, telefon uchun bo'lingan):

ts
test("cookieOl — nomi aniq mos kelgan birinchi cookie", () => {
  assert.equal(
    cookieOl("a=1; __Host-sessiya=abc; b=2", "__Host-sessiya"),
    "abc");
  assert.equal(
    cookieOl("x__Host-sessiya=zz; __Host-sessiya=ok",
      "__Host-sessiya"),
    "ok");
  assert.equal(cookieOl("a=1", "__Host-sessiya"), undefined);
  assert.equal(cookieOl(undefined, "a"), undefined);
});

Biz uni kanon holat nusxasida alohida ishga tushirdik (Node 24.21.0):

bash
node --test --test-name-pattern="cookieOl" test/sessiya.test.ts
text
✔ cookieOl — nomi aniq mos kelgan birinchi cookie (149.1652ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 12282.919

Kanon tajribasidan yana bitta qaror chiqdi — sozlama COOKIE_SECURE (sukut true). Secure va __Host- Chromium'da http://localhost da ham ishlagani uchun lokal dev'da ham prodakshndagi atributlar ishlatiladi. Faqat LAN IP yoki http domen orqali sinaganda false qilinadi; prodakshnda esa false taqiqlanadi. Bu sozlama keyingi darsda config.ts ga qo'shiladi.

Hali commit qilmang — sessiya.ts keyingi darsda sessiya ombori bilan to'ldiriladi va hammasi bitta commit bo'ladi.

11. Real ishda

  • Brauzer DevTools → Application → Cookies: har cookie'ning atributlari, "SameSite" ustuni va rad etilgan cookie'lar sababi (Network panelida, Set-Cookie yonida ogohlantirish belgisi) ko'rinadi. Atribut ishlamasa, birinchi qaraladigan joy.
  • Standart: cookie qoidalari RFC 6265 da. Uning yangi nashri (rfc6265bis — SameSite, prefikslar) hali RFC raqamini olmagan: IETF datatracker'iga ko'ra (2026-10-28 holatiga) oxirgi qoralama — 22-versiya (2025-12-01), hujjat RFC muharriri navbatida. Brauzerlar uni allaqachon amalda qo'llaydi.
  • Sessiya cookie'si nomi: __Host- prefiksli nomlar katta saytlarda keng tarqalgan. Nomning o'zi texnologiyani oshkor qilmasin (connect.sid — Express ekanini aytadi).
  • Intervyuda: "HttpOnly nimadan himoya qiladi va nimadan qilmaydi?", "SameSite=Lax va Strict farqi?", "Sayt va origin farqi?".

Xulosa

  • Set-Cookie — har cookie alohida qator; Node'da getSetCookie().
  • HttpOnly — JS'dan yashiradi, Secure — faqat HTTPS (Chromium'da localhost istisno), Path — xavfsizlik chegarasi emas, Domain — qo'ymaslik xavfsizroq.
  • __Host- = Secure + Path=/ + Domainsiz; aks holda brauzer rad etadi. SameSite=None faqat Secure bilan; 4096 baytdan kattasi saqlanmaydi.
  • SameSite saytga qaraydi (port va subdomen kirmaydi): boshqa saytdan faqat None, havola-GET'da Lax ham; bir saytning boshqa portidan — hammasi, Strict ham.
  • Imzo o'zgarishni sezadi, lekin qiymatni yashirmaydi.
  • Uchinchi tomon cookie'lari brauzerlarda cheklanmoqda; vazifalar cookie'si birinchi tomon.

Keyingi dars: Session-based auth — __Host-sessiya cookie'si, Postgres'dagi sessiyalar jadvali, chiqish va "barcha qurilmalardan chiqish".

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Cookie chuqur: HttpOnly, Secure, SameSite va Host- prefiksi — IlmHamroh