IlmHamroh
JavaScript Full-stack/28-qism. Autentifikatsiya va backend xavfsizligi8/31-dars22 daqiqa
Mundarija (29)

JWT anatomiyasi: imzolangan, lekin shifrlanmagan token

Qisqacha: JWT (JSON Web Token) — uch qismli satr: header.payload.signature. Ichidagi ma'lumot shifrlanmagan — kalitsiz har kim o'qiydi; imzo faqat uni o'zgartirib bo'lmasligini kafolatlaydi. Xavf tekshirishda: kanon tajribasida "o'zimiz yozgan" sodda tekshiruv alg: none tokenni rol: admin bilan qabul qildi, supersecret siri 10 000 so'zli lug'atdan 40.6 ms da topildi. To'g'ri tekshiruv: algoritm server tomonidan qat'iy belgilanadi, exp, iss, aud majburiy, sir — 32 tasodifiy bayt, kutubxona — jose.

Bu darsda

  • JWT'ni qo'lda yasaysiz va ochasiz — Web Crypto HMAC bilan, brauzerda.
  • Claim'lar (sub, exp, iat, iss, aud, jti, nbf) vazifasini va tekshirish tartibini bilasiz.
  • HS256, RS256 va EdDSA farqini o'lchov bilan solishtirasiz.
  • alg: none, algoritm chalkashligi va kuchsiz sir hujumlarini ko'rasiz va yopasiz.
  • jose va jsonwebtoken farqini, JWKS va kid bilan kalit aylantirishni tushuntirasiz.

Oldin bilishingiz kerak: crypto 1: tasodif, hash va HMAC, crypto 2: parolni hash'lash, shifrlash va imzo, Session-based auth, Autentifikatsiyali so'rovlar va token yangilash.

Oldingi darsda server sessiyani o'zi eslab qoldi: brauzerda ma'nosiz token, bazada "bu kim". JWT boshqa yo'ldan boradi: "bu kim" degan ma'lumotning o'zi token ichida, server esa faqat imzoni tekshiradi — bazaga bormasdan. 11-qismda frontend tomonidan access va refresh tokenlarni ishlatgansiz. Bugun ularning ichiga qaraymiz.

1. Nega bu kerak?

JWT juda keng tarqalgan: OAuth va OpenID Connect (Google bilan kirish), mikroservislar orasidagi so'rovlar, mobil ilovalar. Ko'p qo'llanmalar uni "sessiyaning zamonaviy o'rnini bosuvchi" deb taqdim etadi. Shuning uchun uni bilish shart — hatto o'z loyihangizda ishlatmasangiz ham.

Lekin JWT'ning xatolari qimmat. Sessiyada xato tekshiruv odatda "kimdir kira olmadi" bilan tugaydi. JWT'da esa xato tekshiruv "kimdir istalgan odam bo'lib kirdi" bilan tugaydi — chunki token ichidagi sub va rol ga server ishonadi. Bu darsdagi har tuzoq haqiqiy tizimlarda bo'lgan.

2. Uch qism

2.1 Qo'lda yasash

JWT'ni kutubxonasiz, faqat Web Crypto bilan yasaymiz. Har qism — JSON'ning base64url ko'rinishi, imzo — header.payload satrining HMAC-SHA256'i:

js
const enc = new TextEncoder();
const b64url = (baytlar) => btoa(String.fromCharCode(...baytlar))
  .replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
const jsonB64 = (obj) => b64url(enc.encode(JSON.stringify(obj)));

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

const header = jsonB64({ alg: "HS256", typ: "JWT" });
const payload = jsonB64({
  sub: "1", rol: "foydalanuvchi", iat: 1793112206, exp: 1793113106,
});
const imzo = await crypto.subtle.sign(
  "HMAC", kalit, enc.encode(`${header}.${payload}`));
const token = `${header}.${payload}.${b64url(new Uint8Array(imzo))}`;

console.log(token.split(".").map((q) => q.length).join(" + "));
console.log(token);

Konsolda:

text
36 + 90 + 43
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwicm9sIjoiZm95ZGFsYW51dmNoaSIsImlhdCI6MTc5MzExMjIwNiwiZXhwIjoxNzkzMTEzMTA2fQ.S76TnsuLj2s3QUHv0FbifwSC6AXVxb_6iNM6sENuzyc

Uch qism nuqta bilan ajratilgan:

Qism Uzunlik Ichida
header 36 algoritm va tur: {"alg":"HS256","typ":"JWT"}
payload 90 claim'lar — "da'volar": kim, qachongacha
signature 43 32 baytli HMAC, base64url

iat va exp — qat'iy sonlar, sir ham qat'iy, shuning uchun token har safar bir xil chiqadi. base64url — oddiy base64'ning URL'ga xavfsiz varianti: + → -, / → _, oxiridagi = olib tashlanadi. Token URL va sarlavhada qo'shimcha kodlashsiz yuradi.

Kanon xuddi shunday qo'lda yasagan tokenda (email ham bor) uzunlik 207 belgi chiqdi: 36 + 126 + 43. Payload qancha katta bo'lsa, token shuncha uzun — va u har so'rov bilan yuboriladi.

2.2 Kalitsiz o'qish

Endi tokenni kalitsiz ochamiz:

js
const ochiq = (qism) => JSON.parse(atob(
  qism.replaceAll("-", "+").replaceAll("_", "/")));

const token =
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
  ".eyJzdWIiOiIxIiwicm9sIjoiZm95ZGFsYW51dmNoaSIsImlhdCI6" +
  "MTc5MzExMjIwNiwiZXhwIjoxNzkzMTEzMTA2fQ" +
  ".aaaa";
const [h, p] = token.split(".");
console.log(JSON.stringify(ochiq(h)));
console.log(JSON.stringify(ochiq(p)));
console.log(new Date(ochiq(p).exp * 1000).toISOString());

Konsolda:

text
{"alg":"HS256","typ":"JWT"}
{"sub":"1","rol":"foydalanuvchi","iat":1793112206,"exp":1793113106}
2026-10-27T14:58:26.000Z

Imzo o'rniga aaaa qo'ydik — o'qish uchun u umuman kerak emas. Bu darsning eng muhim jumlasi: JWT shifrlanmagan. Uni brauzer kengaytmasi, proksi jurnali, xato xabari — hamma o'qiydi. Payload'ga parol, karta raqami, maxfiy ma'lumot yozilmaydi. Kanon tajribasida ham qo'lda yasalgan token kalitsiz ochildi: email va rol ko'rindi.

Ichini kalitsiz o'qib bo'lmaydigan, ya'ni shifrlangan token formati ham bor — JWE (JSON Web Encryption). Lekin u kam ishlatiladi va bu kursda kerak emas.

2.3 O'zgartirish — imzo buziladi

Imzo nimani kafolatlaydi? O'zgartirilmaganini. Hujumchi payload'da rolni admin ga almashtiradi, imzo esa eskisicha qoladi:

js
const enc = new TextEncoder();
const b64url = (baytlar) => btoa(String.fromCharCode(...baytlar))
  .replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
const baytga = (s) => Uint8Array.from(
  atob(s.replaceAll("-", "+").replaceAll("_", "/")),
  (c) => c.charCodeAt(0));
const kalit = await crypto.subtle.importKey(
  "raw", enc.encode("namuna-sir-faqat-dars-uchun-0002"),
  { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);

async function imzoTogrimi(token) {
  const [h, p, s] = token.split(".");
  return crypto.subtle.verify(
    "HMAC", kalit, baytga(s), enc.encode(`${h}.${p}`));
}

const token =
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
  ".eyJzdWIiOiIxIiwicm9sIjoiZm95ZGFsYW51dmNoaSIsImlhdCI6" +
  "MTc5MzExMjIwNiwiZXhwIjoxNzkzMTEzMTA2fQ" +
  ".S76TnsuLj2s3QUHv0FbifwSC6AXVxb_6iNM6sENuzyc";
console.log(await imzoTogrimi(token));

// hujumchi payload'da rolni o'zgartiradi, imzo eskisicha
const [h, p, s] = token.split(".");
const yangi = JSON.parse(atob(p));
yangi.rol = "admin";
const yangiP = b64url(enc.encode(JSON.stringify(yangi)));
const soxta = `${h}.${yangiP}.${s}`;
console.log(await imzoTogrimi(soxta));

Konsolda:

text
true
false

Imzoni qayta hisoblash uchun sir kerak, hujumchida esa u yo'q. Kanon jose kutubxonasi bilan xuddi shu sinovni qildi: rol=admin li token ERR_JWS_SIGNATURE_VERIFICATION_FAILED bilan rad etildi.

Ko'rinib turibdiki, butun xavfsizlik ikki narsaga tayanadi: sir maxfiy va imzo haqiqatan tekshiriladi. Keyingi bo'limlardagi hujumlar aynan shu ikkisini buzadi.

3. Claim'lar

3.1 Ro'yxatga olingan claim'lar

RFC 7519 bir nechta standart nomni belgilaydi:

Claim Ma'nosi Misol
sub (subject) kim haqida "1" — foydalanuvchi id'si
iss (issuer) kim chiqardi "vazifalar-api"
aud (audience) kim uchun "vazifalar-web"
exp (expiration) qachongacha (Unix soniya) 1793113106
nbf (not before) qachondan boshlab —
iat (issued at) qachon chiqarilgan 1793112206
jti (JWT ID) tokenning noyob id'si qayta ishlatishni aniqlash, bekor qilish ro'yxati

aud ning roli ko'pincha unutiladi. Bitta tizim (masalan, kompaniyaning login serveri) bir nechta xizmat uchun token chiqaradi. Agar aud tekshirilmasa, "hisobot" xizmati uchun chiqarilgan tokenni hujumchi "to'lov" xizmatiga ko'rsatadi — va u qabul qilinadi.

3.2 Kutubxona hammasini tekshiradimi?

Kanon jose 6.2.12 bilan claim'larni sinadi. Har qatorda ikki qismga qarang: chapda — nima sinaldi, strelkadan keyin — kutubxona qabul qildimi yoki rad etdimi:

text
to'g'ri iss/aud: QABUL QILINDI → {"rol":"foydalanuvchi","sub":"1","iss":"vazifalar-api","aud":"vazifalar-web","iat":1793112206,"exp":1793113106,"jti":"8f228e18e5feeac5"}
boshqa audience kutilsa: rad: ERR_JWT_CLAIM_VALIDATION_FAILED: unexpected "aud" claim value
audience BERILMASA (tekshirilmaydi): QABUL QILINDI → {"rol":"foydalanuvchi"," …
exp 10 s oldin: rad: ERR_JWT_EXPIRED: "exp" claim timestamp check failed
exp 10 s oldin, clockTolerance 30 s: QABUL QILINDI → {"ex
jsonwebtoken.decode (TEKSHIRMAYDI!) eskirgan tokenni: {"exp":1793112196}

To'rtta saboq:

  1. exp ni kutubxona o'zi tekshiradi — eskirgan token rad etildi.
  2. aud ni esa siz aytsangizgina tekshiradi. audience parametri berilmasa, token qabul qilindi. iss ham shunday.
  3. clockTolerance: 30 — serverlar soati biroz farq qilishi mumkin; 10 soniya eskirgan token 30 soniyalik chidam bilan qabul qilindi. Chidam kichik bo'lsin (soniyalar, daqiqalar emas).
  4. jsonwebtoken.decode hech narsani tekshirmaydi — eskirgan tokenni ham ochdi. Nomi chalg'itadi: decode — o'qish, verify — tekshirish. Auth kodida faqat verify.

O'zimiz yozgan claim tekshiruvi qanday ko'rinishini brauzerda sinab ko'ring:

js
function claimXatosi(p, kutilgan, hozir, chidam = 0) {
  if (p.iss !== kutilgan.iss) return "iss mos emas";
  const aud = Array.isArray(p.aud) ? p.aud : [p.aud];
  if (!aud.includes(kutilgan.aud)) return "aud mos emas";
  if (typeof p.exp !== "number") return "exp yo'q";
  if (hozir >= p.exp + chidam) return "muddati o'tgan";
  if (p.nbf !== undefined && hozir < p.nbf - chidam) {
    return "hali amal qilmaydi";
  }
  return null;
}

const KUT = { iss: "vazifalar-api", aud: "vazifalar-web" };
const hozir = 1793112206;
const asl = {
  sub: "1", iss: KUT.iss, aud: KUT.aud, exp: hozir + 900,
};
const SINOV = [
  ["to'g'ri", asl, 0],
  ["boshqa xizmat", { ...asl, aud: "boshqa-xizmat" }, 0],
  ["10 s eskirgan", { ...asl, exp: hozir - 10 }, 0],
  ["10 s eskirgan, chidam 30", { ...asl, exp: hozir - 10 }, 30],
  ["exp'siz", { sub: "1", iss: KUT.iss, aud: KUT.aud }, 0],
];
for (const [nom, p, ch] of SINOV) {
  console.log(nom, "→", claimXatosi(p, KUT, hozir, ch) ?? "qabul");
}

Konsolda:

text
to'g'ri → qabul
boshqa xizmat → aud mos emas
10 s eskirgan → muddati o'tgan
10 s eskirgan, chidam 30 → qabul
exp'siz → exp yo'q

Oxirgi qatorga e'tibor bering: exp siz token — rad. RFC'da exp ixtiyoriy, lekin auth uchun muddatsiz token — abadiy kalit. aud massiv ham bo'lishi mumkin (bir token bir nechta xizmat uchun) — shuning uchun Array.isArray.

4. Imzo algoritmlari

4.1 Simmetrik va asimmetrik

Algoritm Turi Kim imzolaydi Kim tekshiradi
HS256 (HMAC-SHA256) simmetrik — bitta sir sir egasi o'sha sir egasi
RS256 (RSA) asimmetrik — juft kalit maxfiy kalit egasi ochiq kalitni bilgan har kim
EdDSA (Ed25519) asimmetrik — juft kalit maxfiy kalit egasi ochiq kalitni bilgan har kim

HS256 da tekshiruvchi ham sirni biladi — demak u ham token yasay oladi. Bitta server o'zi chiqarib o'zi tekshirsa — muammo yo'q. Lekin token o'nta mikroservisda tekshirilsa, har birida sir bor: bittasi buzilsa, hujumchi istalgan token yasaydi. Asimmetrik algoritmda tekshiruvchilarda faqat ochiq kalit: ular token yasay olmaydi.

Ed25519 zamonaviy brauzer va Node'da Web Crypto orqali bor:

js
const enc = new TextEncoder();
const { privateKey, publicKey } = await crypto.subtle.generateKey(
  { name: "Ed25519" }, true, ["sign", "verify"]);

const malumot = enc.encode("header.payload");
const imzo = await crypto.subtle.sign("Ed25519", privateKey, malumot);
console.log(imzo.byteLength);
console.log(await crypto.subtle.verify(
  "Ed25519", publicKey, imzo, malumot));
console.log(await crypto.subtle.verify(
  "Ed25519", publicKey, imzo, enc.encode("header.boshqa")));

// ochiq kalitni JWK ko'rinishida tarqatish mumkin — maxfiy qism yo'q
const jwk = await crypto.subtle.exportKey("jwk", publicKey);
console.log(jwk.kty, jwk.crv, "d" in jwk, jwk.x.length);

Konsolda:

text
64
true
false
OKP Ed25519 false 43

Imzo — 64 bayt. Kalit har safar yangi, shuning uchun uning o'zini emas, xususiyatlarini chiqardik. JWK — kalitning JSON ko'rinishi: kty: "OKP" (Ed25519 oilasi), x — 32 baytli ochiq kalit (base64url'da 43 belgi). Maxfiy qism (d) yo'q — bu JWK'ni bemalol e'lon qilish mumkin. Eski brauzerlarda Ed25519 bo'lmasligi mumkin; Node 24 va joriy Chrome'da bor.

4.2 O'lchov: tezlik va hajm

Kanon jose bilan uchala algoritmni o'lchadi (300 marta, mediana):

JWT: imzolash va tekshirish vaqti (jose, mediana, 300 marta)
  • HS256 imzo0,085 ms
  • HS256 tekshir0,075 ms
  • RS256 imzo0,938 ms
  • RS256 tekshir0,183 ms
  • EdDSA imzo0,127 ms
  • EdDSA tekshir0,195 ms

Manba: Kanon o'lchovi (IlmHamroh, 2026-10): jose 6.2.12, Node 24.21.0, i5-12500H, 2026-10-27

Va token hajmi (bir xil kichik payload bilan): HS256 — 103 belgi, RS256 — 402, EdDSA — 146. RS256 imzosi 256 bayt, Ed25519 niki 64 bayt. Har so'rov sarlavhasida yuradigan token uchun bu farq sezilarli. Hammasi millisekunddan ancha kam — tezlik qaror uchun hal qiluvchi emas. Hal qiluvchisi — kim tekshiradi: bitta server bo'lsa HS256, ko'p xizmat yoki tashqi tomon bo'lsa — EdDSA (yoki eski tizimlar bilan moslik uchun RS256).

5. Hujumlar

5.1 alg: none

JWT standarti "imzosiz token" ni ham taniydi: alg: "none", imzo qismi bo'sh. Bu ataylab qilingan — imzo boshqa yo'l bilan kafolatlangan holatlar uchun. Muammo: ba'zi tekshiruvchilar algoritmni tokendan o'qiydi. Hujumchi o'zi token yasaydi, header'ga none yozadi — va server "imzo kerak emas ekan" deydi.

js
const ochiq = (q) => JSON.parse(atob(
  q.replaceAll("-", "+").replaceAll("_", "/")));
const b64url = (obj) => btoa(JSON.stringify(obj))
  .replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");

// hujumchi yasagan token: imzo yo'q, algoritm "none"
const hujum = `${b64url({ alg: "none", typ: "JWT" })}.` +
  `${b64url({ sub: "1", rol: "admin" })}.`;

// YOMON: sodda tekshiruv header'dagi alg'ga ishonadi
function sodda(token) {
  const [h, p] = token.split(".");
  if (ochiq(h).alg === "none") return ochiq(p); // "imzo kerak emas"
  throw new Error("HMAC tekshiruvi … (bu misolda yetib kelmaydi)");
}

// TO'G'RI: server qaysi algoritmni kutishini O'ZI biladi
const RUXSAT = new Set(["HS256"]);
function qattiq(token) {
  const [h] = token.split(".");
  const { alg } = ochiq(h);
  if (!RUXSAT.has(alg)) {
    throw new Error(`algoritm rad etildi: ${alg}`);
  }
  return "imzo tekshiruviga o'tadi";
}

console.log(JSON.stringify(sodda(hujum)));
try {
  qattiq(hujum);
} catch (x) {
  console.log(x.message);
}

Konsolda:

text
{"sub":"1","rol":"admin"}
algoritm rad etildi: none

Sodda tekshiruv hujumchini admin qilib qabul qildi. Qoida: algoritm — server konfiguratsiyasi, tokenning "taklifi" emas. Ruxsat ro'yxati (allowlist) bilan.

Kanon tajribasida kutubxonalar o'zini qanday tutdi. Har qatorning oxiriga qarang — "QABUL" yoki "rad":

text
sodda tekshiruv: QABUL QILINDI → {"sub":"1","rol":"admin"}
jose.jwtVerify: rad: ERR_JOSE_NOT_SUPPORTED: alg none is not supported either by JOSE or your javascript runtime
jose.UnsecuredJWT.decode (faqat ataylab): QABUL QILINDI → {"sub":"1","rol":"admin"}
jsonwebtoken.verify (algorithms'siz): rad: JsonWebTokenError: jwt signature is required
jsonwebtoken.verify (algorithms: ['none'], sir bilan): rad: JsonWebTokenError: jwt signature is required

Ikkala zamonaviy kutubxona ham none ni verify da rad etdi. jose da imzosiz tokenni o'qish uchun alohida, nomi ogohlantiradigan API bor (UnsecuredJWT) — tasodifan ishlatib bo'lmaydi. Xavf kutubxonada emas, o'zimiz yozgan "kichik yordamchi" funksiyada.

5.2 Algoritm chalkashligi

Ikkinchi klassik hujum: server RS256 kutadi va tekshirish uchun ochiq kalitni beradi. Hujumchi header'ga HS256 yozadi va tokenni HMAC bilan imzolaydi — sir sifatida ochiq kalitning PEM matnidan foydalanib (u hammaga ochiq!). Eski kutubxona "algoritm HS256 ekan, kalit sifatida berilgan narsani HMAC siri qilib olaman" desa — imzo mos keladi.

Kanon natijasi:

text
jsonwebtoken.verify(token, publicKeyPEM) algorithms'siz: rad: JsonWebTokenError: invalid algorithm
jsonwebtoken.verify(…, { algorithms: ['RS256'] }): rad: JsonWebTokenError: invalid algorithm
jose.jwtVerify(token, publicKey KeyObject): rad: TypeError: KeyObject instances must be of type "secret" for the HS256 algorithm

jsonwebtoken 9 algoritmni kalit turiga qarab cheklaydi; jose esa kalit turini tekshiradi: HS256 uchun maxfiy kalit kerak, ochiq kalit — yo'q. Ikkalasi ham himoyalangan, lekin bu kutubxonalarning joriy versiyalari. Himoya qatlami baribir sizda bo'lsin: algorithms: ["EdDSA"] ni doim aniq yozing.

5.3 Kuchsiz sir

HS256 ning kuchi — sirning kuchi. Hujumchi bitta haqiqiy tokenni (masalan, o'zining) qo'lga kiritsa, sirni offline — serverga hech qanday so'rovsiz — tera boshlaydi. Brauzerda sinab ko'ring:

js
const enc = new TextEncoder();
const b64url = (buf) =>
  btoa(String.fromCharCode(...new Uint8Array(buf)))
    .replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");

async function hs256(sir, matn) {
  const k = await crypto.subtle.importKey("raw", enc.encode(sir),
    { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
  const s = await crypto.subtle.sign("HMAC", k, enc.encode(matn));
  return b64url(s);
}

// hujumchi qo'lidagi token (sir — "supersecret", unga noma'lum)
const qism = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0";
const imzo = await hs256("supersecret", qism);

// lug'at: 999 ta "parolN" va bitta mashhur so'z
const lugat = Array.from({ length: 999 }, (_, i) => `parol${i}`);
lugat.push("supersecret");
let urinish = 0;
for (const soz of lugat) {
  urinish++;
  if ((await hs256(soz, qism)) === imzo) {
    console.log(`sir topildi: "${soz}", urinish: ${urinish}`);
    break;
  }
}

Konsolda:

text
sir topildi: "supersecret", urinish: 1000

Kanon xuddi shu hujumni Node'da 10 000 so'zli lug'at bilan qildi: supersecret 40.6 ms da topildi (bitta CPU yadrosi). Sir topilgach, hujumchi istalgan sub va rol bilan token yasaydi — server farqini bilmaydi. Hujjatlardagi secret, jwt_secret, changeme kabi namunalar ataylab lug'atlarga kiritilgan.

Himoya: sir — 32 tasodifiy bayt (randomBytes(32)), .env da yoki sir menejerida (Sirlarni boshqarish). 2^256 variantni terib bo'lmaydi. Asimmetrik algoritmda bu muammo umuman yo'q — maxfiy kalitni lug'atdan topib bo'lmaydi.

Tekshirib ko'ring: Hujumchi sirni topish uchun serverga nechta so'rov yuboradi? Tezlik cheklovi (rate limiting) bu hujumni to'xtata oladimi?

Javob

Birorta ham. Unga faqat bitta haqiqiy token kerak — qolgan hamma ish uning o'z kompyuterida, HMAC'ni qayta-qayta hisoblash. Tezlik cheklovi, jurnal, CAPTCHA — hech biri buni ko'rmaydi. Yagona himoya — sirning o'zi terib bo'lmaydigan darajada tasodifiy bo'lishi.

6. To'g'ri tekshirish tartibi

flowchart TD
  T[Token keldi] --> F{3 qism, base64url, JSON?}
  F -->|yo'q| R[401]
  F --> A{header.alg ruxsat ro'yxatidami?}
  A -->|yo'q| R
  A --> K{kid bo'yicha kalit topildimi?}
  K -->|yo'q| R
  K --> I{Imzo to'g'rimi?}
  I -->|yo'q| R
  I --> C{exp, nbf, iss, aud}
  C -->|xato| R
  C --> O[Payload'ga ishonish mumkin]

Tartib muhim: payload'dagi biror narsaga (masalan, sub bo'yicha bazadan foydalanuvchi qidirishga) faqat imzo va claim'lar tekshirilgandan keyin tegiladi. Kutubxona (jose.jwtVerify) bu bosqichlarni o'zi bajaradi — siz faqat parametrlarni to'g'ri berasiz:

js
import { jwtVerify } from "jose";

const { payload } = await jwtVerify(token, ochiqKalit, {
  algorithms: ["EdDSA"],      // ruxsat ro'yxati — doim aniq
  issuer: "vazifalar-api",
  audience: "vazifalar-web",
  clockTolerance: 5,          // soniya, kichik
});

6.1 jose yoki jsonwebtoken

jose jsonwebtoken
muhit Node, brauzer, Deno, edge (Web Crypto) faqat Node
API Promise, SignJWT / jwtVerify callback yoki sinxron
EdDSA, JWKS ichida (createRemoteJWKSet) qo'shimcha paketlar
none verify da yo'q, alohida UnsecuredJWT verify rad etadi

Kanon tajribasi ikkalasini sinadi (jose 6.2.12, jsonwebtoken 9.0.3); yangi loyiha uchun jose qulayroq — zamonaviy, bog'liqliksiz va Web Crypto'ga asoslangan.

7. JWKS va kid: kalitni almashtirish

Kalitlar abadiy emas: sizib chiqishi mumkin, yoki shunchaki rejali almashtiriladi. Lekin eski kalit bilan imzolangan tokenlar hali amal qilyapti. Yechim — JWKS (JSON Web Key Set): ochiq kalitlar ro'yxati, har birida kid (key ID). Token header'ida kid turadi — tekshiruvchi ro'yxatdan mos kalitni topadi. OpenID provayderlari (Google va boshqalar) o'z JWKS'ini .well-known manzilida e'lon qiladi.

Kanon tajribasi — almashtirish ssenariysi:

text
kid=2026-10 token, JWKS [2026-10, 2026-11]: QABUL QILINDI → {"sub"
eski kalit olib tashlangandan keyin: rad: ERR_JWKS_NO_MATCHING_KEY: no applicable key found in the JSON Web Key Set
JWKS ichida faqat ochiq kalit maydonlari: crv,x,kty,kid,alg,use

Almashtirish tartibi shundan kelib chiqadi:

  1. Yangi kalit (2026-11) JWKS'ga qo'shiladi, lekin hali imzolash uchun ishlatilmaydi.
  2. Server yangi tokenlarni yangi kalit bilan imzolay boshlaydi. Eski tokenlar ham tekshiriladi — ikkala kalit ro'yxatda.
  3. Eng uzun token muddati o'tgach, eski kalit ro'yxatdan olib tashlanadi. Shundan keyin eski kalitli token rad etiladi.

Uchinchi qator — JWKS'da faqat ochiq maydonlar (crv, x, kty, kid, alg, use). Maxfiy qism (d) ro'yxatga hech qachon tushmasin — kodda JWK'ni eksport qilishda shunga e'tibor bering.

8. Hujumchi nigohi

Oxirgi ustun — himoyani shu darsning qaysi sinovida o'z ko'zingiz bilan ko'rganingiz.

Hujum Nima uchun ishlaydi Himoya Qayerda sinadik
payload'dan maxfiy ma'lumot o'qish JWT shifrlanmagan payload'da sir yo'q «Kalitsiz o'qish»
rol: admin ga o'zgartirish — imzo tekshiruvi (kutubxona) «O'zgartirish — imzo buziladi»: false
alg: none algoritm tokendan o'qiladi algorithms: [...] qat'iy qattiq(): rad
ochiq kalit bilan HS256 imzo algoritm chalkashligi algoritm va kalit turi qat'iy kanon: ikkala kutubxona rad
sirni lug'atdan terish (offline) kuchsiz HS256 siri 32 tasodifiy bayt yoki EdDSA 1000-urinishda topildi
boshqa xizmat tokenini ko'rsatish aud tekshirilmagan audience majburiy claimXatosi: "aud mos emas"
decode ni tekshiruv deb ishlatish decode imzoga qaramaydi faqat verify kanon: eskirgan token ochildi
muddatsiz token exp ixtiyoriy exp yo'q — rad claimXatosi: "exp yo'q"

9. Ko'p uchraydigan xatolar

Xato Nega xavfli To'g'risi
"JWT shifrlangan" deb o'ylash email, rol, id ochiq payload — ochiq ma'lumot
jwt.decode bilan auth imzo tekshirilmaydi jwtVerify / jwt.verify
algorithms ni bermaslik tokendagi alg ga ishonch algorithms: ["EdDSA"]
JWT_SECRET=secret lug'atdan millisekundlarda randomBytes(32)
audience, issuer siz tekshiruv boshqa xizmat tokeni o'tadi ikkalasi ham aniq
katta payload (profil, ruxsatlar ro'yxati) har so'rovda yuzlab bayt faqat sub va kerakli minimum
o'z JWT kutubxonangizni yozish none, chalkashlik, timing jose

10. Mashqlar

Mashqlar kurs/mashqlar/28/08-jwt/ da.

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

  1. JWT payload'ini o'qish uchun imzo kaliti kerak.
  2. HS256 tokenni tekshira oladigan server uni yasay ham oladi.
  3. jose.jwtVerify da audience berilmasa, aud claim'i tekshirilmaydi.
  4. alg: none hujumiga qarshi eng yaxshi himoya — tokenni base64 emas, hex bilan kodlash.
  5. JWKS'da maxfiy kalitlar ham bo'lishi mumkin, chunki u faqat serverlar uchun.
Yechim
  1. Noto'g'ri — payload oddiy base64url; kalitsiz ochiladi.
  2. To'g'ri — HS256 simmetrik: tekshirish va imzolash bitta sir bilan.
  3. To'g'ri — kanon tajribasida audience siz token qabul qilindi.
  4. Noto'g'ri — kodlash ahamiyatsiz; algoritmni server qat'iy belgilashi kerak (ruxsat ro'yxati).
  5. Noto'g'ri — JWKS ommaviy e'lon qilinadi; unda faqat ochiq kalitlar.

2-mashq (o'rta): tokenni ochish

jwtOch(token, hozir) funksiyasini yozing: token 3 qismdan iborat bo'lmasa — { xato: "3 qism emas" }; header yoki payload base64url/JSON sifatida buzuq bo'lsa — { xato: "base64url yoki JSON buzuq" }; aks holda { alg, sub, qolgan }, bu yerda qolgan — exp gacha qolgan soniyalar (exp yo'q bo'lsa null). Imzoni tekshirmang — bu faqat "ichida nima bor" vositasi. To'rtta tokenda sinang. Ishora: buzuq JSON — try/catch (try / catch / finally).

Yechim
js
function jwtOch(token, hozir) {
  const qismlar = token.split(".");
  if (qismlar.length !== 3) return { xato: "3 qism emas" };
  try {
    const [header, payload] = qismlar.slice(0, 2).map((q) =>
      JSON.parse(atob(q.replaceAll("-", "+").replaceAll("_", "/"))));
    const qolgan =
      payload.exp === undefined ? null : payload.exp - hozir;
    return { alg: header.alg, sub: payload.sub, qolgan };
  } catch {
    return { xato: "base64url yoki JSON buzuq" };
  }
}

const T =
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
  ".eyJzdWIiOiIxIiwicm9sIjoiZm95ZGFsYW51dmNoaSIsImlhdCI6" +
  "MTc5MzExMjIwNiwiZXhwIjoxNzkzMTEzMTA2fQ" +
  ".S76TnsuLj2s3QUHv0FbifwSC6AXVxb_6iNM6sENuzyc";
console.log(JSON.stringify(jwtOch(T, 1793112206)));
console.log(JSON.stringify(jwtOch(T, 1793113206)));
console.log(JSON.stringify(jwtOch("abc.def", 0)));
console.log(JSON.stringify(jwtOch("@@@.e30.x", 0)));

Konsolda:

text
{"alg":"HS256","sub":"1","qolgan":900}
{"alg":"HS256","sub":"1","qolgan":-100}
{"xato":"3 qism emas"}
{"xato":"base64url yoki JSON buzuq"}

Ikkinchi qator: qolgan: -100 — token 100 soniya oldin eskirgan, lekin jwtOch uni baribir ochdi. Bu jsonwebtoken.decode ning xulqi: o'qish va tekshirish — boshqa-boshqa ishlar. Bunday funksiya faqat jurnal va diagnostika uchun; frontend esa undan "token qachon eskiradi" ni bilib, oldindan yangilash uchun foydalanadi. @@@ — base64'da yo'q belgilar, atob xato tashlaydi va catch uni ushlaydi.

3-mashq (qiyin): o'z jwtTekshir

jwtTekshir(token, kalit, kut, hozir) ni yozing — HS256 uchun to'liq tekshiruv, darsdagi diagramma tartibida: shakl ({ xato: "shakl" }), algoritm faqat HS256 ({ xato: "alg: …" }), imzo (crypto.subtle.verify, { xato: "imzo" }), iss va aud ({ xato: "iss/aud" }), exp majburiy va o'tmagan ({ xato: "muddat" }). Muvaffaqiyatda { sub }. Sinov uchun tokenlarni o'zingiz yasang: to'g'ri, alg: none, buzilgan imzo, boshqa aud, eskirgan va bo'sh satr. Ishora: imzo yasash — «Qo'lda yasash» bo'limidagi kod. Bu mashq faqat tushunish uchun — haqiqiy loyihada jose.

Yechim
js
const enc = new TextEncoder();
const baytga = (s) => Uint8Array.from(
  atob(s.replaceAll("-", "+").replaceAll("_", "/")),
  (c) => c.charCodeAt(0));
const json = (q) => JSON.parse(new TextDecoder().decode(baytga(q)));

async function jwtTekshir(token, kalit, kut, hozir) {
  const q = token.split(".");
  if (q.length !== 3) return { xato: "shakl" };
  let header, payload;
  try {
    [header, payload] = [json(q[0]), json(q[1])];
  } catch {
    return { xato: "shakl" };
  }
  if (header.alg !== "HS256") return { xato: `alg: ${header.alg}` };
  const togri = await crypto.subtle.verify("HMAC", kalit,
    baytga(q[2]), enc.encode(`${q[0]}.${q[1]}`));
  if (!togri) return { xato: "imzo" };
  if (payload.iss !== kut.iss || payload.aud !== kut.aud) {
    return { xato: "iss/aud" };
  }
  if (typeof payload.exp !== "number" || hozir >= payload.exp) {
    return { xato: "muddat" };
  }
  return { sub: payload.sub };
}

const b64 = (b) => btoa(String.fromCharCode(...b))
  .replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
const kalit = await crypto.subtle.importKey(
  "raw", enc.encode("namuna-sir-faqat-dars-uchun-0003"),
  { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
async function yasa(header, payload) {
  const hp = `${b64(enc.encode(JSON.stringify(header)))}.` +
    b64(enc.encode(JSON.stringify(payload)));
  const s = await crypto.subtle.sign("HMAC", kalit, enc.encode(hp));
  return `${hp}.${b64(new Uint8Array(s))}`;
}

const KUT = { iss: "vazifalar-api", aud: "vazifalar-web" };
const hozir = 1793112206;
const P = { sub: "1", ...KUT, exp: hozir + 900 };
const yaxshi = await yasa({ alg: "HS256" }, P);
const SINOV = [
  ["to'g'ri", yaxshi],
  ["alg none", await yasa({ alg: "none" }, P)],
  ["imzo buzilgan", yaxshi.slice(0, -2) + "xx"],
  ["boshqa aud", await yasa({ alg: "HS256" }, { ...P, aud: "x" })],
  ["eskirgan", await yasa({ alg: "HS256" }, { ...P, exp: hozir })],
  ["bo'sh satr", ""],
];
for (const [nom, t] of SINOV) {
  const n = await jwtTekshir(t, kalit, KUT, hozir);
  console.log(nom, "→", JSON.stringify(n));
}

Konsolda:

text
to'g'ri → {"sub":"1"}
alg none → {"xato":"alg: none"}
imzo buzilgan → {"xato":"imzo"}
boshqa aud → {"xato":"iss/aud"}
eskirgan → {"xato":"muddat"}
bo'sh satr → {"xato":"shakl"}

alg none tokeni haqiqiy HMAC imzosi bilan yasaldi. Baribir rad: algoritm tekshiruvi imzodan oldin turadi. exp: hozir — chegara holati: "aynan hozir" eskirgan hisoblanadi (>=). json() da TextDecoder ishlatdik — payload'da o'zbekcha harflar (o', kirill) bo'lsa ham to'g'ri ochiladi; atob ning o'zi baytlarni harf sifatida qaytaradi.

11. Loyiha qadami: JWT kanonga kirmadi

Bu darsda vazifalar-api ga kod qo'shilmaydi. Kanon JWT'ni tajriba sifatida sinadi (yuqoridagi barcha jose va jsonwebtoken natijalari — shu tajribadan), lekin loyihada server sessiyasi qoldi. Sababi qisqacha:

  • Frontend va API bitta saytda — JWT'ning asosiy afzalligi (bazasiz tekshirish, ko'p xizmat orasida tarqatish) bizga kerak emas.
  • Sessiyani darhol bekor qilish mumkin; JWT esa muddati tugaguncha amal qiladi.
  • JWT bilan xato qilish oson: yuqoridagi to'rtta hujumning har biri — bitta noto'g'ri parametr.

Kanon bu qarorni o'lchovlar bilan alohida hujjatda (ADR — Architecture Decision Record) yozdi. Uni Session vs JWT qarori va uzoq sessiyalar darsida to'liq ko'ramiz. Undan oldin keyingi darsda JWT'ning eng og'ir savoliga — "token o'g'irlansa nima bo'ladi?" — refresh tokenlar bilan javob qidiramiz va kanon buni ham haqiqiy kod bilan o'lchagan.

O'z kompyuteringizda JWT bilan tajriba qilmoqchi bo'lsangiz — vazifalar-api dan alohida papkada:

bash
npm init -y
npm i jose

va «To'g'ri tekshirish tartibi» bo'limidagi jwtVerify namunasidan boshlang. vazifalar-api ga commit qilinadigan narsa yo'q.

12. Real ishda

  • OpenID Connect (Google, Microsoft bilan kirish) id_token ni JWT ko'rinishida beradi. Uni tekshirish — aynan shu dars: provayderning JWKS'i, iss, aud (sizning client id'ingiz), exp, nonce. Bu OAuth 2.0 va OpenID Connect nazariyasi darsida.
  • jwt.io kabi saytlarga haqiqiy (prodakshn) tokenni joylamang — u ochiq ma'lumot, lekin ishlaydigan kalit.
  • RFC 8725 (JWT Best Current Practices) — yuqoridagi hujumlarning rasmiy ro'yxati va tavsiyalar: algoritmni qat'iy belgilash, aud ni tekshirish, kuchli kalitlar.
  • Intervyuda: "JWT shifrlanganmi?", "alg: none hujumi nima?", "HS256 va RS256 qachon?", "JWT'ni qanday bekor qilasiz?".

Xulosa

  • JWT = header.payload.signature, har qism base64url; payload ochiq, imzo faqat o'zgarmaslikni kafolatlaydi.
  • Claim'lar: sub, iss, aud, exp, nbf, iat, jti; exp ni kutubxona tekshiradi, aud va iss ni — faqat siz aytsangiz.
  • HS256 — bitta sir (tekshiruvchi ham yasay oladi), RS256/EdDSA — juft kalit; kanon o'lchovida hammasi < 1 ms, EdDSA tokeni RS256 nikidan ancha qisqa (146 va 402 belgi).
  • Hujumlar: alg: none, algoritm chalkashligi, kuchsiz sir (supersecret — 40.6 ms), decode ni tekshiruv deb bilish.
  • Himoya: algorithms qat'iy, issuer/audience aniq, sir — 32 tasodifiy bayt, jose, JWKS + kid bilan kalit aylantirish.

Keyingi dars: Access va refresh token strategiyasi — qisqa access, uzoq refresh, rotatsiya va o'g'irlangan tokenni aniqlash.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JWT anatomiyasi: imzolangan, lekin shifrlanmagan token — IlmHamroh