Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Login va tokenlar
- 2.1 /kirish
- 2.2 Token ichida nima bor
- 3. Authorization: Bearer
- 3.1 Himoyalangan so'rov
- 3.2 Muddat tugashini ko'ramiz
- 4. Jim yangilash (silent refresh)
- 4.1 Bitta so'rov uchun
- 4.2 Muammo: bir vaqtda uchta 401
- 4.3 Yechim: yangilash navbati
- 5. Axios interceptor bilan
- 6. Cookie bilan autentifikatsiya
- 6.1 Brauzer o'zi yuboradi
- 6.2 credentials
- 6.3 Token qayerda saqlanadi?
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 8.1 Bearer siz yoki bo'sh joysiz
- 8.2 Sarlavhani bir marta yasash
- 8.3 Har 401 da alohida yangilash
- 8.4 Cheksiz yangilash sikli
- 8.5 finally siz navbat
- 8.6 403 da yangilash
- 9. Mashqlar
- 1-mashq (oson): Kirish xatosi
- 2-mashq (o'rta): Token eskirdimi?
- 3-mashq (qiyin): Yangilash muvaffaqiyatsiz bo'lsa — chiqish
- 10. Real ishda
- Xulosa
- Manbalar
Token bilan so'rovlar: Authorization sarlavhasi va token yangilash navbati
Qisqacha: Login'dan keyin server ikki token beradi: qisqa muddatli kirish tokeni va uzoq muddatli yangilash tokeni. Har so'rovga
Authorization: Bearer <kirish tokeni>sarlavhasi qo'shiladi. Server 401 qaytarsa — yangilash tokeni bilan yangi juft olinadi va so'rov qayta yuboriladi. Bir vaqtda bir nechta 401 kelsa, yangilash bir marta bajarilishi kerak: hamma so'rov bitta umumiy Promise'ni kutadi.
Bu darsda
- Autentifikatsiya va avtorizatsiya farqini, kirish va yangilash tokenlari nega ikkita ekanini tushuntira olasiz.
- Login qilib,
Authorization: Bearersarlavhasi bilan himoyalangan so'rov yuborasiz va 401 sabablarini o'qiysiz. - Muddati tugagan tokenni foydalanuvchi sezmasdan (jim) yangilaysiz.
- Bir vaqtda kelgan uchta 401 ni bitta yangilash bilan hal qiladigan navbat yozasiz —
fetchda ham, Axios interceptor'ida ham. - Cookie asosidagi autentifikatsiyada
fetchningcredentialssozlamasini to'g'ri qo'yasiz va token qayerda saqlanishini tanlaysiz.
Oldin bilishingiz kerak: Axios, API klient qatlami, Request, Response, Headers va fetch opsiyalari, Promise kombinatorlari, CORS mijoz tomondan.
1. Nega bu kerak?
HTTP xotirasiz: har so'rov server uchun yangi tanishuv. Siz hozirgina login qildingiz, keyingi so'rovda esa server "siz kimsiz?" deydi. Har so'rovda parol yuborish — xavfli va noqulay.
Katta bir binoni tasavvur qiling. Kirishda qo'riqchi bor. Birinchi kuni kadrlar bo'limiga pasport ko'rsatasiz va ikki narsa olasiz:
- Bir kunlik propusk. Uni har eshikda qo'riqchiga ko'rsatasiz. Yo'qotsangiz — zarar katta emas: ertaga u baribir yaroqsiz.
- Shaxsiy guvohnoma. U uzoq muddatli. Uni faqat kadrlar bo'limida, propusk muddati tugaganda yangisini olish uchun ko'rsatasiz. Har eshikda olib yurmaysiz.
Veb ilovalar ham shunday ishlaydi. Propusk — kirish tokeni (access token), guvohnoma — yangilash tokeni (refresh token). Token — server bergan, sizni tanitadigan va soxtalashtirib bo'lmaydigan qisqa matn.
Ikki atamani ajratib olaylik:
- Autentifikatsiya (authentication) — "siz kimsiz?" savoliga javob: login va parol, token. Qo'riqchi propuskdagi ismni tekshiradi.
- Avtorizatsiya (authorization) — "sizga bu yerga kirish mumkinmi?" Propuskingiz bor, lekin direktor xonasiga ruxsat yo'q.
Bugungi dars birinchisi haqida: brauzer so'rovlariga "men — Aziz" degan isbotni to'g'ri qo'shish va u eskirganda jim yangilash. Bu har bir login'li ilovada bor: Telegram Web, Payme, Uzum Market — ular sizni har 15 daqiqada qayta login qildirmaydi.
2. Login va tokenlar
2.1 /kirish
Mashq API'da sinov foydalanuvchisi bor: login aziz, parol bahor123. ?muddat= — kirish tokenining umri soniyalarda (5 dan 300 gacha; standart 30):
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
async function kir(login, parol) {
const javob = await fetch(`${ASOSIY_URL}/kirish?muddat=60`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ login, parol }),
});
const malumot = await javob.json();
console.log(javob.status, Object.keys(malumot));
return malumot;
}
const tokenlar = await kir("aziz", "bahor123");
console.log("Muddat:", tokenlar.muddat, "soniya");
await kir("aziz", "noto'g'ri");Konsolda:
200 [ 'kirishToken', 'yangilashToken', 'muddat' ]
Muddat: 60 soniya
401 [ 'xato' ]Muvaffaqiyatda uch maydon: kirishToken, yangilashToken va muddat. Noto'g'ri parolda — 401 va { xato: "Login yoki parol noto'g'ri" }. Server "login yo'q" yoki "parol xato" deb farqlamaydi — bu ataylab: aks holda hujumchi qaysi loginlar borligini bilib olardi.
Mashq API'da yangilash tokeni 600 soniya (10 daqiqa) yashaydi. Real ilovalarda odatda kirish tokeni 5–15 daqiqa, yangilash tokeni — kunlar yoki haftalar.
2.2 Token ichida nima bor
Tokenni oching — u ikki qism, nuqta bilan ajratilgan: eyJzdWIi... . kD3x.... Birinchi qism — yuk (payload): Base64url bilan kodlangan oddiy JSON. Base64'ni Typed arrays va binar ma'lumot darsida ko'rgansiz: baytlarni harf va raqamlar bilan yozish usuli, shifrlash emas. Base64url — uning URL'ga mos varianti.
Ikkinchi qism — imzo (signature): server sirli kalit bilan hisoblagan "muhr". Bu so'zni funksiya imzosi (parametrlar ro'yxati) bilan adashtirmang — bu yerda imzo hujjatdagi muhr ma'nosida.
Yukni istalgan kishi o'qiy oladi:
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
const javob = await fetch(`${ASOSIY_URL}/kirish?muddat=60`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ login: "aziz", parol: "bahor123" }),
});
const { kirishToken } = await javob.json();
const [yuk, imzo] = kirishToken.split(".");
const base64 = yuk.replaceAll("-", "+").replaceAll("_", "/");
const malumot = JSON.parse(atob(base64));
console.log(malumot.sub, malumot.tur);
console.log("Umri:", malumot.exp - malumot.iat, "soniya");
console.log("Imzo uzunligi:", imzo.length);Konsolda:
aziz kirish
Umri: 60 soniya
Imzo uzunligi: 43atob — Base64 matnni oddiy matnga aylantiradigan brauzer funksiyasi (Node'da ham bor). Base64url'da + va / o'rniga - va _ ishlatiladi — URL'da xavfsiz bo'lishi uchun. Shuning uchun avval ularni qaytardik.
Yukda: sub — kim (subject), tur — qaysi token, iat — qachon berilgan (issued at), exp — qachon tugaydi (expires). Vaqtlar — 1970 yildan beri o'tgan soniyalar.
Ikki muhim xulosa:
- Token shifrlanmagan. Uni ochish uchun hech qanday kalit kerak emas. Tokenga parol, karta raqami kabi maxfiy narsa yozilmaydi.
- Token soxtalashtirilmaydi. Yukdagi
subni"admin"ga o'zgartirsangiz, imzo mos kelmaydi va server "Token yaroqsiz" deydi. Imzoni faqat sirli kalitni biladigan server yasay oladi.
Siz ishda uchratadigan JWT (JSON Web Token) xuddi shunday, faqat uch qismli: sarlavha, yuk, imzo. Mashq API soddaroq, ikki qismli shaklni ishlatadi. JWT'ni to'liq JWT anatomiyasi darsida, backend qismida ko'ramiz.
Tekshirib ko'ring: Kimdir sizning kirish tokeningizni ko'chirib oldi. U tokenning yukidagi
expni uzaytirib, abadiy ishlatishi mumkinmi?
Javob
Yo'q. Yuk o'zgarsa, imzo unga mos kelmaydi — server tokenni rad etadi. Lekin exp tugaguncha o'g'irlangan token ishlaydi. Kirish tokenining umri qisqa bo'lishining sababi shu: o'g'irlansa ham, zarar bir necha daqiqa bilan cheklanadi.
3. Authorization: Bearer
3.1 Himoyalangan so'rov
Token serverga Authorization sarlavhasida yuboriladi. Shakli: Bearer so'zi, bitta bo'sh joy, keyin token. "Bearer" — "egasi": "bu tokenni ko'rsatgan odamga ishon" degani.
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
const kirish = await fetch(`${ASOSIY_URL}/kirish`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ login: "aziz", parol: "bahor123" }),
});
const { kirishToken } = await kirish.json();
async function profilniOl(sarlavha) {
const javob = await fetch(`${ASOSIY_URL}/profil`, {
headers: sarlavha ? { Authorization: sarlavha } : {},
});
console.log(javob.status, await javob.json());
}
await profilniOl(`Bearer ${kirishToken}`);
await profilniOl();
await profilniOl(kirishToken);
await profilniOl("Bearer buzilgan.token");Konsolda:
200 { login: 'aziz', ism: 'Aziz' }
401 { xato: "Token yo'q" }
401 { xato: 'Token yaroqsiz' }
401 { xato: 'Token yaroqsiz' }To'rt holat:
- To'g'ri sarlavha — profil keldi.
- Sarlavha yo'q — "Token yo'q".
Bearerso'zisiz faqat token — "Token yaroqsiz". Server shaklni tekshiradi; so'zni unutish — eng ko'p uchraydigan xato.- Buzilgan token — imzo mos emas.
Muddati tugagan token esa o'zining xabarini beradi: Token muddati tugagan. Uni keyingi bo'limda jonli ko'ramiz.
401 javobida server yana bir sarlavha qo'shadi: WWW-Authenticate: Bearer — "menga Bearer token kerak". 401 Unauthorized — "siz kim ekaningizni isbotlamadingiz". Uni 403 Forbidden dan farqlang: "sizni taniyman, lekin bunga ruxsatingiz yo'q". 401 da token yangilashga urinish mantiqli, 403 da esa — yo'q: yangi token ham ruxsat bermaydi.
3.2 Muddat tugashini ko'ramiz
Natijada 5 soniyalik token olamiz. Profilni darhol so'raymiz, keyin 6 soniya kutib yana so'raymiz. Ikkinchi urinish 401 bersa — yangilash tokeni bilan yangi juft olamiz va uchinchi marta so'raymiz. Kutish paytida har soniya konsolga yoziladi:
<p>Natija konsolda ↓ (taxminan 7 soniya)</p>
<script>
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
const kut = (ms) => new Promise((tayyor) => setTimeout(tayyor, ms));
async function jsonYubor(yol, tana) {
const javob = await fetch(ASOSIY_URL + yol, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(tana),
});
return javob.json();
}
async function profil(token) {
const javob = await fetch(`${ASOSIY_URL}/profil`, {
headers: { Authorization: `Bearer ${token}` },
});
const malumot = await javob.json();
return `${javob.status} ${malumot.ism ?? malumot.xato}`;
}
async function sina() {
const t = await jsonYubor("/kirish?muddat=5", {
login: "aziz", parol: "bahor123",
});
console.log("Kirdik. Darhol:", await profil(t.kirishToken));
for (let s = 1; s <= 6; s++) {
await kut(1000);
console.log(`${s} s...`);
}
console.log("Eski token:", await profil(t.kirishToken));
const yangi = await jsonYubor("/yangila", {
yangilashToken: t.yangilashToken,
});
console.log("Yangi token:", await profil(yangi.kirishToken));
}
sina();
</script>Konsolda:
Kirdik. Darhol: 200 Aziz
1 s...
2 s...
3 s...
4 s...
5 s...
6 s...
Eski token: 401 Token muddati tugagan
Yangi token: 200 AzizFoydalanuvchi nuqtai nazaridan bu to'liq jarayon: token eskirdi, server 401 dedi, ilova yangilash tokeni bilan /yangila dan yangi juft oldi va ish davom etdi. Endi uni avtomatik — foydalanuvchi sezmaydigan qilib yozamiz.
Tekshirib ko'ring: Nega kirish tokenini darhol 10 daqiqa emas, 5 soniya qildik? Real ilovada 5 soniya to'g'rimi?
Javob
Darsda muddat tugashini kutib o'tirmaslik uchun. Real ilovada 5 soniya juda qisqa: har 5 soniyada yangilash so'rovi ketadi, server yuklanadi. Odatda 5–15 daqiqa tanlanadi — o'g'irlangan token uchun yetarlicha qisqa, yangilash uchun yetarlicha uzoq.
4. Jim yangilash (silent refresh)
4.1 Bitta so'rov uchun
Jim yangilash (silent refresh) — token eskirganda foydalanuvchiga hech narsa ko'rsatmay, uni yangilab, so'rovni qayta yuborish. Algoritm:
- So'rovni joriy kirish tokeni bilan yuboring.
- Javob 401 bo'lsa —
/yangilaga yangilash tokenini yuboring va yangi juftni saqlang. - So'rovni yangi token bilan bir marta qayta yuboring.
- Yangilash ham muvaffaqiyatsiz bo'lsa — foydalanuvchini tizimdan chiqaring ("Qaytadan kiring").
Sinash uchun bir hiyla ishlatamiz. Token muddati tugashini 5 soniya kutish o'rniga, login'dan keyin kirish tokenini ataylab buzamiz. Server uchun bu ham 401 — ilova uchun farqi yo'q:
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
const tokenlar = { kirish: null, yangilash: null };
async function jsonYubor(yol, tana) {
return fetch(ASOSIY_URL + yol, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(tana),
});
}
function saqla(juft) {
tokenlar.kirish = juft.kirishToken;
tokenlar.yangilash = juft.yangilashToken;
}
async function tokenniYangila() {
console.log("↻ token yangilanmoqda");
const javob = await jsonYubor("/yangila", {
yangilashToken: tokenlar.yangilash,
});
if (!javob.ok) throw new Error("Qaytadan kiring");
saqla(await javob.json());
}
async function himoyalangan(yol) {
const yubor = () => fetch(ASOSIY_URL + yol, {
headers: { Authorization: `Bearer ${tokenlar.kirish}` },
});
let javob = await yubor();
if (javob.status === 401) {
await tokenniYangila();
javob = await yubor();
}
return javob.json();
}
saqla(await (await jsonYubor("/kirish", {
login: "aziz", parol: "bahor123",
})).json());
tokenlar.kirish = "eskirgan-token"; // sinov: token "eskirdi"
console.log((await himoyalangan("/profil")).ism);
console.log((await himoyalangan("/profil")).ism);Konsolda:
↻ token yangilanmoqda
Aziz
AzizBirinchi so'rov 401 oldi, token yangilandi va so'rov qayta ketdi. Ikkinchisi esa yangi token bilan birinchi urinishdayoq o'tdi — yangilash bo'lmadi.
yubor — funksiya, chunki so'rovni ikki marta yaratamiz va ikkinchi marta tokenlar.kirish allaqachon yangi. Sarlavhani funksiyadan tashqarida bir marta yasasangiz, qayta urinish ham eski token bilan ketardi.
4.2 Muammo: bir vaqtda uchta 401
Sahifa ochilganda ilova odatda bir nechta so'rovni parallel yuboradi: profil, bildirishnomalar, buyurtmalar. Token eskirgan bo'lsa — uchalasi ham 401 oladi. Yuqoridagi himoyalangan bilan nima bo'ladi?
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
const tokenlar = { kirish: null, yangilash: null };
let yangilashlar = 0;
async function jsonYubor(yol, tana) {
return fetch(ASOSIY_URL + yol, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(tana),
});
}
function saqla(juft) {
tokenlar.kirish = juft.kirishToken;
tokenlar.yangilash = juft.yangilashToken;
}
async function tokenniYangila() {
yangilashlar++;
const javob = await jsonYubor("/yangila", {
yangilashToken: tokenlar.yangilash,
});
if (!javob.ok) throw new Error("Qaytadan kiring");
saqla(await javob.json());
}
async function himoyalangan(yol) {
const yubor = () => fetch(ASOSIY_URL + yol, {
headers: { Authorization: `Bearer ${tokenlar.kirish}` },
});
let javob = await yubor();
if (javob.status === 401) {
await tokenniYangila();
javob = await yubor();
}
return javob.json();
}
saqla(await (await jsonYubor("/kirish", {
login: "aziz", parol: "bahor123",
})).json());
tokenlar.kirish = "eskirgan-token";
await Promise.all([
himoyalangan("/profil"),
himoyalangan("/profil"),
himoyalangan("/profil"),
]);
console.log("Yangilashlar:", yangilashlar); // Yangilashlar: 3Uchta 401 — uchta yangilash (konsoldagi 3). Mashq API'da bu baribir ishladi, lekin real serverlarda ikki jiddiy muammo bor:
- Bekorga yuk. 20 ta parallel so'rov — 20 ta yangilash.
- Rotatsiya (rotation). Xavfsiz serverlar har yangilashda yangi yangilash tokeni beradi va eskisini o'chiradi. Birinchi yangilash eski tokenni "yoqib yuboradi". Ikkinchi va uchinchisi esa shu eski token bilan keladi — server ularni o'g'irlik deb biladi va foydalanuvchini butunlay tizimdan chiqaradi. Aziz sahifani ochdi va... login sahifasiga tushdi.
4.3 Yechim: yangilash navbati
G'oya: yangilash bitta Promise. Birinchi 401 uni boshlaydi va o'zgaruvchiga saqlaydi. Qolgan 401 lar yangi yangilash boshlamaydi — o'sha Promise'ni kutadi. Promise tugagach, o'zgaruvchi tozalanadi.
sequenceDiagram
participant A as Profil
participant B as Buyurtmalar
participant Y as Yangilash
participant S as Server
A->>S: GET (eski token)
B->>S: GET (eski token)
S-->>A: 401
A->>Y: yangilanish yo'q — boshlayman
S-->>B: 401
B->>Y: yangilanish bor — kutaman
Y->>S: POST /yangila
S-->>Y: yangi juft
Y-->>A: tayyor
Y-->>B: tayyor
A->>S: GET (yangi token)
B->>S: GET (yangi token)Diagrammada "Buyurtmalar" ikkinchi yangilashni boshlamaydi — u allaqachon ketayotganini ko'radi va kutadi. Kod:
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
const tokenlar = { kirish: null, yangilash: null };
let yangilashlar = 0;
let yangilanish = null;
async function jsonYubor(yol, tana) {
return fetch(ASOSIY_URL + yol, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(tana),
});
}
function saqla(juft) {
tokenlar.kirish = juft.kirishToken;
tokenlar.yangilash = juft.yangilashToken;
}
async function tokenniYangila() {
yangilashlar++;
const javob = await jsonYubor("/yangila", {
yangilashToken: tokenlar.yangilash,
});
if (!javob.ok) throw new Error("Qaytadan kiring");
saqla(await javob.json());
}
function bittaYangila() {
if (!yangilanish) {
yangilanish = tokenniYangila().finally(() => {
yangilanish = null;
});
}
return yangilanish;
}
async function himoyalangan(yol) {
const yubor = (token) => fetch(ASOSIY_URL + yol, {
headers: { Authorization: `Bearer ${token}` },
});
const ishlatilgan = tokenlar.kirish;
let javob = await yubor(ishlatilgan);
if (javob.status === 401) {
if (tokenlar.kirish === ishlatilgan) await bittaYangila();
javob = await yubor(tokenlar.kirish);
}
return javob.json();
}
saqla(await (await jsonYubor("/kirish", {
login: "aziz", parol: "bahor123",
})).json());
tokenlar.kirish = "eskirgan-token";
const profillar = await Promise.all([
himoyalangan("/profil"),
himoyalangan("/profil"),
himoyalangan("/profil"),
]);
console.log(profillar.map((p) => p.ism));
console.log("Yangilashlar soni:", yangilashlar);Konsolda:
[ 'Aziz', 'Aziz', 'Aziz' ]
Yangilashlar soni: 1Uchta so'rov, bitta yangilash. Uch qism muhim:
bittaYangila— yangilash ketayotgan bo'lsa (yangilanishbor), o'shani qaytaradi. Yo'q bo'lsa — boshlaydi. Bitta Promise'ni istalganchaawaitqilish mumkin — hammasi bir xil natijani oladi (Promise asoslari: Promise bir marta hal bo'ladi)..finally(...)— yangilash tugagach (muvaffaqiyatli yoki xato bilan), o'zgaruvchi tozalanadi. Aks holda keyingi safar token yana eskirganda, eski — allaqachon tugagan — Promise qaytarilardi va yangilash bo'lmasdi.ishlatilgantokenni solishtirish. Sekin so'rov 401 ni yangilash tugagandan keyin olishi mumkin. Unda token allaqachon yangi:tokenlar.kirish !== ishlatilgan. Yana yangilash shart emas — faqat yangi token bilan qayta yuborish kifoya.
Bu naqsh — Asinxron naqshlar dagi "bir xil ishni bir marta bajarish" (request deduplication) ning amaliy ko'rinishi.
5. Axios interceptor bilan
Axios darsida interceptor'larni "nazorat punkti" dedik. Token uchun ular ayniqsa qulay: so'rov interceptor'i har so'rovga token qo'shadi, javob interceptor'i 401 ni ushlab, yangilab, qayta yuboradi. Interfeys kodi esa tokenlar haqida umuman bilmaydi:
<p>Natija konsolda ↓</p>
<script type="module">
import axios from "https://cdn.jsdelivr.net/npm/axios@1.20.0/+esm";
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
const tokenlar = { kirish: null, yangilash: null };
let yangilanish = null;
let yangilashlar = 0;
const api = axios.create({ baseURL: ASOSIY_URL, timeout: 8000 });
async function tokenniYangila() {
yangilashlar++;
const { data } = await axios.post(`${ASOSIY_URL}/yangila`, {
yangilashToken: tokenlar.yangilash,
});
tokenlar.kirish = data.kirishToken;
tokenlar.yangilash = data.yangilashToken;
}
function bittaYangila() {
yangilanish ??= tokenniYangila().finally(() => {
yangilanish = null;
});
return yangilanish;
}
api.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${tokenlar.kirish}`;
return config;
});
api.interceptors.response.use(
(javob) => javob,
async (xato) => {
const config = xato.config;
if (xato.response?.status !== 401 || config.qaytaYuborildi) {
return Promise.reject(xato);
}
config.qaytaYuborildi = true;
await bittaYangila();
return api(config);
},
);
const { data } = await axios.post(`${ASOSIY_URL}/kirish`, {
login: "aziz", parol: "bahor123",
});
tokenlar.kirish = "eskirgan-token";
tokenlar.yangilash = data.yangilashToken;
const javoblar = await Promise.all([
api.get("/profil"), api.get("/profil"), api.get("/profil"),
]);
console.log(javoblar.map((j) => j.data.ism));
console.log("Yangilashlar soni:", yangilashlar);
</script>Konsolda:
(3) ['Aziz', 'Aziz', 'Aziz']
Yangilashlar soni: 1Uch tafsilot:
return api(config)— Axios instance'ni funksiya sifatida chaqirsa bo'ladi: u shu sozlama bilan so'rovni qayta yuboradi. So'rov yana so'rov interceptor'idan o'tadi va yangi tokenni oladi. Javob interceptor'ining natijasi — qayta yuborilgan so'rovning javobi.await api.get(...)uni oddiy javob kabi oladi.config.qaytaYuborildi— o'zimiz qo'ygan belgi. Yangi token bilan ham 401 kelsa (masalan, foydalanuvchi bloklangan), cheksiz "401 → yangila → 401" aylanishi bo'lmaydi: ikkinchi 401 to'g'ridan-to'g'ricatchga ketadi.- Yangilash
apiorqali emas, oddiyaxios.postbilan./yangilaham 401 qaytarishi mumkin (yangilash tokeni eskirgan). Uapiorqali ketsa, interceptor uni ham ushlab, yana yangilashga urinardi — o'zini o'zi kutib qotib qoladigan holat.
DevTools konsolida esa uchta qizil GET .../profil 401 qatorini ko'rasiz. Bu — brauzerning tarmoq yozuvi, kodingizdagi xato emas: 401 lar ushlandi va so'rovlar qayta muvaffaqiyatli ketdi (Network panelida uchta 401 va uchta 200).
Operator ??= — "chap tomon null yoki undefined bo'lsa, o'ngdagini yoz" (??, ?. va mantiqiy tayinlash). if (!yangilanish) yangilanish = ... ning qisqasi.
Tekshirib ko'ring: Javob interceptor'idan
config.qaytaYuborilditekshiruvini olib tashlasak va server foydalanuvchini bloklagan bo'lsa (har doim 401), nima bo'ladi?
Javob
Cheksiz sikl: 401 → yangilash → qayta yuborish → yana 401 → yana yangilash ... Brauzer serverga to'xtovsiz so'rov yog'diradi, mashq API esa daqiqasiga 120 tadan keyin 429 bilan to'xtatadi. Belgi har so'rovga faqat bitta qayta urinish beradi.
6. Cookie bilan autentifikatsiya
6.1 Brauzer o'zi yuboradi
Tokenni JavaScript o'zgaruvchisida saqlab, sarlavhaga qo'lda qo'yish — bir yo'l. Ikkinchi, ko'p saytlarda uchraydigan yo'l — cookie. Server login javobida Set-Cookie sarlavhasi bilan kichik matn yuboradi, brauzer uni saqlaydi va shu saytga har so'rovga o'zi qo'shadi. JavaScript hech narsa qilmaydi.
Cookie'ning eng muhim belgisi — HttpOnly: bunday cookie'ni JavaScript umuman ko'rmaydi va o'qiy olmaydi. Sahifaga begona skript tushib qolsa ham, tokenni o'g'irlay olmaydi. Cookie'larni to'liq Cookie'lar JS'dan darsida ko'ramiz.
6.2 credentials
fetch cookie'ni qachon yuborishini credentials sozlamasi belgilaydi (Request, Response, Headers):
credentials |
Cookie qachon ketadi |
|---|---|
"same-origin" (standart) |
faqat o'z saytingizga |
"include" |
boshqa saytga ham |
"omit" |
hech qachon |
Frontend app.example.com da, API esa api.example.com da bo'lsa — bu boshqa origin. Cookie ketishi uchun:
const javob = await fetch("https://api.example.com/profil", {
credentials: "include",
});Lekin bu yetarli emas. CORS mijoz tomondan darsidagi qoidani eslang: server ham ruxsat berishi kerak. Cookie'li so'rovda server javobida ikki sarlavha bo'lishi shart:
Access-Control-Allow-Credentials: true;Access-Control-Allow-Origin: https://app.example.com— aniq manzil,*emas.
* bilan cookie'li so'rov ishlamaydi — brauzer javobni bloklaydi. Mashq API shuning uchun cookie ishlatmaydi: u hamma saytga ochiq (*) bo'lishi kerak. Axios'da xuddi shu sozlama — withCredentials: true.
6.3 Token qayerda saqlanadi?
Jadvalda bitta yangi so'z bor — CSRF: begona sayt sizning brauzeringiz nomidan, cookie bilan so'rov yuborishi. Cookie har so'rovga o'zi qo'shilgani uchun shunday hujum mumkin bo'ladi.
| Joy | XSS (begona skript) | Sahifa yangilansa | Izoh |
|---|---|---|---|
| JS o'zgaruvchisi (xotira) | o'g'irlash qiyin | yo'qoladi | yangilash tokeni bilan qayta olinadi |
localStorage |
o'g'irlanadi | saqlanadi | eng oddiy, eng xavfli |
HttpOnly cookie |
o'qib bo'lmaydi | saqlanadi | CSRF'dan himoya kerak |
Ko'p zamonaviy ilovalar aralash yo'l tanlaydi: kirish tokeni — xotirada (o'zgaruvchida), yangilash tokeni — HttpOnly cookie'da. Sahifa yangilanganda ilova /yangila ga murojaat qiladi, brauzer cookie'ni o'zi qo'shadi va yangi kirish tokeni xotiraga tushadi.
localStorage ni localStorage va sessionStorage darsida, ikki darsdan keyin o'rganamiz. CSRF himoyasi — SameSite belgisi va boshqalar — ni Tokenni qayerda saqlash va CSRF darsida, backend qismida ko'ramiz.
7. Hujumchi nigohi
Token — eshik kaliti. Hujumchi uni olishning to'rt yo'lini sinaydi:
- XSS orqali o'g'irlash. Sahifaga begona skript tushsa (masalan, sharh
innerHTMLbilan chiqarilsa), ulocalStorage.getItem("token")ni o'qib, o'z serveriga yuboradi. Himoya: foydalanuvchi matni — faqattextContent(Kontentni o'zgartirish va XSS xavfi); uzoq muddatli token —HttpOnlycookie'da; kirish tokeni — qisqa muddatli. - Token URL'da.
?token=eyJ...— URL server loglarida, brauzer tarixida,Referersarlavhasida qoladi va boshqa saytlarga sizib chiqadi. Himoya: token faqatAuthorizationsarlavhasida yoki cookie'da. Hech qachon URL'da emas. - Konsol va loglar.
console.log(tokenlar)— ishlab chiqarishda qolib ketsa, xatolarni yig'adigan xizmatlarga (Sentry kabi) token ham tushadi. Himoya: tokenni hech qachon loglamang; xato xabariga sarlavhalarni qo'shmang. - Yangilash tokenini ko'p joyga yuborish. Yangilash tokeni har so'rovda ketsa, o'g'irlanish ehtimoli o'sha so'rovlar soniga ko'payadi. Himoya: yangilash tokeni faqat
/yangilaga ketadi — bizning koddagi kabi. Rotatsiya bilan esa o'g'irlangan eski token ishlatilsa, server buni sezadi (Access va refresh token strategiyasi).
Va bitta umumiy qoida: yangilash muvaffaqiyatsiz bo'lsa — tokenlarni o'chiring va login sahifasiga yuboring. Eskirgan tokenlarni "balki ishlab qolar" deb saqlab qolmang.
8. Ko'p uchraydigan xatolar
8.1 Bearer siz yoki bo'sh joysiz
Authorization: token yoki Bearer${token} — server "Token yaroqsiz" deydi. Tuzatish: aynan `Bearer ${token}` — so'z, bitta bo'sh joy, token.
8.2 Sarlavhani bir marta yasash
const sarlavha = { Authorization: ... } ni funksiya boshida bir marta yasab, qayta urinishda ham ishlatish — qayta urinish eski token bilan ketadi va yana 401. Tuzatish: sarlavhani har yuborishda joriy tokendan yasang (yubor() funksiyasi kabi).
8.3 Har 401 da alohida yangilash
Parallel so'rovlar — bir nechta yangilash, rotatsiyali serverda esa majburiy logout. Tuzatish: bittaYangila navbati.
8.4 Cheksiz yangilash sikli
/yangila ning o'zi 401 qaytaradi, interceptor uni ham ushlaydi va yana yangilaydi. Yoki yangi token bilan ham 401 kelaveradi. Tuzatish: yangilash so'rovi interceptor'li instance'dan o'tmasin; har so'rovga faqat bitta qayta urinish (qaytaYuborildi).
8.5 finally siz navbat
yangilanish o'zgaruvchisi tozalanmasa, keyingi safar u allaqachon bajarilgan Promise'ni qaytaradi — token yangilanmaydi, hamma so'rov 401 bilan qaytadi. Tuzatish: .finally(() => { yangilanish = null; }).
8.6 403 da yangilash
403 — "ruxsat yo'q", token esa yaroqli. Yangilash befoyda va yuk. Tuzatish: faqat 401 da yangilang.
9. Mashqlar
1-mashq (oson): Kirish xatosi
kir(login, parol) funksiyasini yozing: muvaffaqiyatda tokenlar juftini qaytarsin, 401 da Error tashlasin — matni serverning xato maydonidan. aziz/bahor123 va aziz/12345 bilan sinang; birinchisida muddat ni, ikkinchisida xato matnini chiqaring.
Yechim
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
async function kir(login, parol) {
const javob = await fetch(`${ASOSIY_URL}/kirish`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ login, parol }),
});
const malumot = await javob.json();
if (!javob.ok) {
throw new Error(malumot.xato);
}
return malumot;
}
const juft = await kir("aziz", "bahor123");
console.log("Muddat:", juft.muddat); // Muddat: 30
try {
await kir("aziz", "12345");
} catch (xato) {
console.log(xato.message); // Login yoki parol noto'g'ri
}?muddat= berilmadi — server standart 30 soniyani berdi.
2-mashq (o'rta): Token eskirdimi?
Ba'zi ilovalar 401 ni kutmay, token tugashiga oz qolganda oldindan yangilaydi. tokenEskirdimi(token, hozir, zaxira) funksiyasini yozing: tokenning yukidagi exp ni o'qisin va exp - zaxira <= hozir bo'lsa true qaytarsin (hozir va zaxira — soniyalarda). Tarmoqsiz sinash uchun tokenni o'zingiz yasang: yuk — {"sub":"aziz","exp":1000} ning Base64url ko'rinishi, imzo — istalgan matn.
Ishora: btoa — atob ning teskarisi (matn → Base64).
Yechim
function yukniOqi(token) {
const yuk = token.split(".")[0];
const base64 = yuk.replaceAll("-", "+").replaceAll("_", "/");
return JSON.parse(atob(base64));
}
function tokenEskirdimi(token, hozir, zaxira = 0) {
const { exp } = yukniOqi(token);
return exp - zaxira <= hozir;
}
const yuk = btoa(JSON.stringify({ sub: "aziz", exp: 1000 }))
.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
const token = `${yuk}.soxta-imzo`;
console.log(tokenEskirdimi(token, 900)); // false
console.log(tokenEskirdimi(token, 1000)); // true
console.log(tokenEskirdimi(token, 990, 30)); // trueUchinchi qator: 990 da tokenning 10 soniyasi qoldi, zaxira esa 30 — yangilash vaqti keldi. replaceAll("=", "") — Base64url odatda oxiridagi = to'ldiruvchilarsiz yoziladi.
Muhim: bu tekshiruv faqat qulaylik uchun. Foydalanuvchi kompyuterining soati noto'g'ri bo'lishi mumkin, shuning uchun 401 da yangilash baribir kerak. Va imzoni brauzerda tekshirib bo'lmaydi — buni faqat server qiladi.
3-mashq (qiyin): Yangilash muvaffaqiyatsiz bo'lsa — chiqish
«Yangilash navbati» bo'limidagi kodni o'zgartiring: /yangila muvaffaqiyatsiz bo'lsa, chiqish() funksiyasi chaqirilsin — u tokenlarni null qilsin va "Qaytadan kiring" deb yozsin. Bir vaqtda kelgan uchta so'rov uchun chiqish() bir marta chaqirilsin. Sinash uchun login'dan keyin ikkala tokenni ham buzing. Promise.allSettled (Promise kombinatorlari) bilan uchala natija holatini chiqaring. Faylni kurs/mashqlar/11/24-token/chiqish.mjs ga saqlang.
Yechim
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
const tokenlar = { kirish: null, yangilash: null };
let yangilanish = null;
async function jsonYubor(yol, tana) {
return fetch(ASOSIY_URL + yol, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(tana),
});
}
function saqla(juft) {
tokenlar.kirish = juft.kirishToken;
tokenlar.yangilash = juft.yangilashToken;
}
function chiqish() {
tokenlar.kirish = null;
tokenlar.yangilash = null;
console.log("Qaytadan kiring");
}
async function tokenniYangila() {
const javob = await jsonYubor("/yangila", {
yangilashToken: tokenlar.yangilash,
});
if (!javob.ok) {
chiqish();
throw new Error("Sessiya tugadi");
}
saqla(await javob.json());
}
function bittaYangila() {
yangilanish ??= tokenniYangila().finally(() => {
yangilanish = null;
});
return yangilanish;
}
async function himoyalangan(yol) {
const yubor = (token) => fetch(ASOSIY_URL + yol, {
headers: { Authorization: `Bearer ${token}` },
});
const ishlatilgan = tokenlar.kirish;
let javob = await yubor(ishlatilgan);
if (javob.status === 401) {
if (tokenlar.kirish === ishlatilgan) await bittaYangila();
javob = await yubor(tokenlar.kirish);
}
return javob.json();
}
saqla(await (await jsonYubor("/kirish", {
login: "aziz", parol: "bahor123",
})).json());
tokenlar.kirish = "eskirgan";
tokenlar.yangilash = "eskirgan";
const natijalar = await Promise.allSettled([
himoyalangan("/profil"),
himoyalangan("/profil"),
himoyalangan("/profil"),
]);
for (const n of natijalar) {
console.log(n.status, n.reason?.message);
}Konsolda:
Qaytadan kiring
rejected Sessiya tugadi
rejected Sessiya tugadi
rejected Sessiya tugadichiqish() bir marta chaqirildi, chunki u tokenniYangila ichida, tokenniYangila esa navbat tufayli bir marta ishladi. Uchala so'rov ham bir xil rad etilgan Promise'ni kutgan edi — hammasi bir xil xatoni oldi. Promise.allSettled hech birini yo'qotmay, uchala natijani ham ko'rsatdi.
git add 11/24-token/chiqish.mjs
git commit -m "11/24: yangilash muvaffaqiyatsizda bir marta chiqish"10. Real ishda
- SPA'lar (bitta sahifali ilovalar — oddiy SPA router darsida o'zimiz yasaymiz). React, Vue ilovalarida aynan shu Axios interceptor yoki
fetcho'rami + yangilash navbati yoziladi. React'da auth holatini butun ilovaga tarqatish — SPA'da autentifikatsiya oqimi. - Mobil ilovalar. Payme, Click, Uzum ilovalari ham kirish va yangilash tokenlari bilan ishlaydi — shuning uchun ularda har kuni qayta login qilmaysiz. Mexanizm bir xil, faqat tokenlar telefonning xavfsiz xotirasida.
- Backend tomoni. Tokenlarni qanday yaratish, imzolash, rotatsiya va o'g'irlikni aniqlash — Access va refresh token strategiyasi. "Sizga token umuman kerakmi yoki sessiya yetadimi?" — Session vs JWT qarori.
- Intervyu. "Access va refresh token farqi?", "Token qayerda saqlanadi?", "Bir nechta 401 kelsa nima qilasiz?" — o'rta darajali (middle) frontend dasturchi intervyusining klassik savollari.
Xulosa
- Autentifikatsiya — kim ekanini isbotlash, avtorizatsiya — nimaga ruxsat borligi. 401 — "kimligingiz isbotlanmadi", 403 — "ruxsat yo'q".
- Kirish tokeni — qisqa muddatli, har so'rovda
Authorization: Bearer <token>. Yangilash tokeni — uzoq muddatli, faqat/yangilaga. - Token yuki shifrlanmagan (Base64url), lekin imzo uni soxtalashtirishdan saqlaydi.
- Jim yangilash: 401 → yangilash → yangi token bilan bir marta qayta yuborish → muvaffaqiyatsiz bo'lsa chiqish.
- Parallel 401 lar — bitta umumiy yangilash Promise'i (
bittaYangila),finallybilan tozalanadi. - Cookie'li auth:
credentials: "include"+ server tomonda aniq origin vaAllow-Credentials;HttpOnlycookie'ni JS o'qiy olmaydi.
Keyingi dars: WebSocket va Server-Sent Events — so'rov-javobdan doimiy ulanishga: server o'zi xabar yuborganda brauzer uni qanday oladi.
Manbalar
- RFC 6750: "The OAuth 2.0 Authorization Framework: Bearer Token Usage" — rfc-editor.org
- MDN: "Authorization", "WWW-Authenticate", "401 Unauthorized", "403 Forbidden", "Request: credentials" — developer.mozilla.org
- OWASP: "JSON Web Token Cheat Sheet", "HTML5 Security Cheat Sheet" (localStorage va tokenlar) — cheatsheetseries.owasp.org
- Axios hujjatlari: "Interceptors" — axios-http.com/docs/interceptors
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!