Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Uch qism
- 2.1 Qo'lda yasash
- 2.2 Kalitsiz o'qish
- 2.3 O'zgartirish — imzo buziladi
- 3. Claim'lar
- 3.1 Ro'yxatga olingan claim'lar
- 3.2 Kutubxona hammasini tekshiradimi?
- 4. Imzo algoritmlari
- 4.1 Simmetrik va asimmetrik
- 4.2 O'lchov: tezlik va hajm
- 5. Hujumlar
- 5.1 alg: none
- 5.2 Algoritm chalkashligi
- 5.3 Kuchsiz sir
- 6. To'g'ri tekshirish tartibi
- 6.1 jose yoki jsonwebtoken
- 7. JWKS va kid: kalitni almashtirish
- 8. Hujumchi nigohi
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): to'g'ri yoki noto'g'ri
- 2-mashq (o'rta): tokenni ochish
- 3-mashq (qiyin): o'z jwtTekshir
- 11. Loyiha qadami: JWT kanonga kirmadi
- 12. Real ishda
- Xulosa
- Manbalar
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 tekshiruvalg: nonetokennirol: adminbilan qabul qildi,supersecretsiri 10 000 so'zli lug'atdan 40.6 ms da topildi. To'g'ri tekshiruv: algoritm server tomonidan qat'iy belgilanadi,exp,iss,audmajburiy, 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.josevajsonwebtokenfarqini, JWKS vakidbilan 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:
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:
36 + 90 + 43
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwicm9sIjoiZm95ZGFsYW51dmNoaSIsImlhdCI6MTc5MzExMjIwNiwiZXhwIjoxNzkzMTEzMTA2fQ.S76TnsuLj2s3QUHv0FbifwSC6AXVxb_6iNM6sENuzycUch 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:
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:
{"alg":"HS256","typ":"JWT"}
{"sub":"1","rol":"foydalanuvchi","iat":1793112206,"exp":1793113106}
2026-10-27T14:58:26.000ZImzo 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:
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:
true
falseImzoni 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:
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:
expni kutubxona o'zi tekshiradi — eskirgan token rad etildi.audni esa siz aytsangizgina tekshiradi.audienceparametri berilmasa, token qabul qilindi.issham shunday.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).jsonwebtoken.decodehech narsani tekshirmaydi — eskirgan tokenni ham ochdi. Nomi chalg'itadi:decode— o'qish,verify— tekshirish. Auth kodida faqatverify.
O'zimiz yozgan claim tekshiruvi qanday ko'rinishini brauzerda sinab ko'ring:
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:
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'qOxirgi 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:
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:
64
true
false
OKP Ed25519 false 43Imzo — 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):
- 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.
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:
{"sub":"1","rol":"admin"}
algoritm rad etildi: noneSodda 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":
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 requiredIkkala 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:
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 algorithmjsonwebtoken 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:
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:
sir topildi: "supersecret", urinish: 1000Kanon 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:
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:
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,useAlmashtirish tartibi shundan kelib chiqadi:
- Yangi kalit (
2026-11) JWKS'ga qo'shiladi, lekin hali imzolash uchun ishlatilmaydi. - Server yangi tokenlarni yangi kalit bilan imzolay boshlaydi. Eski tokenlar ham tekshiriladi — ikkala kalit ro'yxatda.
- 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
- JWT payload'ini o'qish uchun imzo kaliti kerak.
- HS256 tokenni tekshira oladigan server uni yasay ham oladi.
jose.jwtVerifydaaudienceberilmasa,audclaim'i tekshirilmaydi.alg: nonehujumiga qarshi eng yaxshi himoya — tokenni base64 emas, hex bilan kodlash.- JWKS'da maxfiy kalitlar ham bo'lishi mumkin, chunki u faqat serverlar uchun.
Yechim
- Noto'g'ri — payload oddiy base64url; kalitsiz ochiladi.
- To'g'ri — HS256 simmetrik: tekshirish va imzolash bitta sir bilan.
- To'g'ri — kanon tajribasida
audiencesiz token qabul qilindi. - Noto'g'ri — kodlash ahamiyatsiz; algoritmni server qat'iy belgilashi kerak (ruxsat ro'yxati).
- 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
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:
{"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
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:
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:
npm init -y
npm i joseva «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_tokenni 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,
audni tekshirish, kuchli kalitlar. - Intervyuda: "JWT shifrlanganmi?", "
alg: nonehujumi 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;expni kutubxona tekshiradi,audvaissni — 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),decodeni tekshiruv deb bilish. - Himoya:
algorithmsqat'iy,issuer/audienceaniq, sir — 32 tasodifiy bayt,jose, JWKS +kidbilan kalit aylantirish.
Keyingi dars: Access va refresh token strategiyasi — qisqa access, uzoq refresh, rotatsiya va o'g'irlangan tokenni aniqlash.
Manbalar
- RFC 7519 "JSON Web Token (JWT)": https://www.rfc-editor.org/rfc/rfc7519
- RFC 7515 "JSON Web Signature (JWS)": https://www.rfc-editor.org/rfc/rfc7515 ; RFC 7517 "JSON Web Key (JWK)": https://www.rfc-editor.org/rfc/rfc7517
- RFC 8725 "JSON Web Token Best Current Practices": https://www.rfc-editor.org/rfc/rfc8725
- OWASP JSON Web Token Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html
- jose (panva): https://github.com/panva/jose ; jsonwebtoken: https://github.com/auth0/node-jsonwebtoken
- MDN — SubtleCrypto (HMAC, Ed25519): https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto
- Kanon o'lchovi (IlmHamroh, 2026-10-27): JWT tajribasi (jose 6.2.12, jsonwebtoken 9.0.3, Node 24.21.0, i5-12500H). Brauzer misollari — biz ishga tushirdik (2026-10-28, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!