IlmHamroh
JavaScript Full-stack/28-qism. Autentifikatsiya va backend xavfsizligi14/31-dars21 daqiqa
Mundarija (39)

OAuth 2.0 va OpenID Connect: authorization code, PKCE, state, nonce va id_token

Qisqacha: OAuth 2.0 — "parolingizni bermasdan" boshqa saytga cheklangan ruxsat berish protokoli. OpenID Connect (OIDC) uning ustiga kimlik qatlamini qo'shadi: provayder (Google, Telegram) "bu foydalanuvchi — kim" degan imzolangan id_token beradi. Bugungi yagona to'g'ri oqim — authorization code + PKCE: brauzer faqat bir martalik code oladi, uni tokenga server almashtiradi. Uch himoya: state — login CSRF'dan, PKCE — o'g'irlangan kod ishlatilishidan. nonce esa id_token bilan takroriy hujumdan (replay attack — eski tokenni qayta yuborish) saqlaydi. Implicit oqim (response_type=token) eskirgan.

Bu darsda

  • OAuth'ning to'rt rolini va ular orasidagi ishonchni tushuntira olasiz.
  • Authorization code + PKCE oqimini qadamma-qadam chizasiz va har parametr nima uchun kerakligini aytasiz.
  • id_token va access token farqini, scope va claim'larni bilasiz.
  • Discovery hujjatini (.well-known/openid-configuration) o'qiy olasiz.
  • Lokal soxta provayderda yetti hujumning qanday rad etilganini ko'rasiz.

Oldin bilishingiz kerak: JWT anatomiyasi, Session-based auth, Tokenni qayerda saqlash va CSRF, Tashqi login va SMS kod bilan kirish UI (PKCE'ni brauzerda hisoblagansiz).

Oldingi darslarda vazifalar-api ning o'z kirish usullarini qurdik: parol, email havolasi, SMS kod. Hammasida foydalanuvchi bizga o'zini isbotlaydi. Endi boshqa savol: foydalanuvchi Google'da allaqachon kirgan bo'lsa, nega bizda yana parol o'ylab topsin? Buning protokoli — OAuth 2.0 va OpenID Connect. Bu dars — nazariya va lokal tajriba; kodni keyingi darsda loyihaga qo'shamiz.

1. Nega bu kerak?

1.1 Parolni so'rash davri

2000-yillar oxirida ijtimoiy tarmoqlar "do'stlaringizni toping" tugmasini qo'yardi. Tugma Gmail parolingizni so'rardi: sayt pochtangizga kirib, kontaktlarni o'qiydi. Bu xavfli edi:

  • sayt kontaktlardan tashqari hamma xatni ham o'qiy oladi;
  • parolni sayt bazasida saqlaydi — u sizsa, pochta ham ketadi;
  • ruxsatni qaytarib olishning yagona yo'li — parolni almashtirish.

1.2 Mehmonxona kartasi

Mehmonxonaga keldingiz. Qabulxona sizga karta beradi: u faqat sizning xonangizni va faqat chiqish kunigacha ochadi. Kartani yo'qotsangiz, uni bekor qilishadi — mehmonxonaning bosh kaliti xavf ostida qolmaydi.

OAuth aynan shunday ishlaydi. Parol (bosh kalit) faqat Google'da qoladi. Boshqa saytga esa token (karta) beriladi: cheklangan huquq, cheklangan muddat, alohida bekor qilish. Standart nomi — RFC 6749 (2012).

1.3 Bizning ehtiyoj: kimlik

vazifalar ga Google pochtangizni o'qish kerak emas. Bizga bitta narsa kerak: "bu odam — Google'dagi qaysi foydalanuvchi va uning emaili qanday". OAuth buni o'zi aytmaydi — u ruxsat haqida. Kimlik uchun ustiga qurilgan qatlam bor: OpenID Connect (2014, OpenID Foundation). Keyingi bo'limlarda ikkalasini ajratamiz.

2. To'rt rol

2.1 Kim kim

OAuth'da to'rt ishtirokchi bor. vazifalar dagi "Google bilan kirish" misolida:

Rol Inglizcha Bizda
Resurs egasi resource owner Aziz (foydalanuvchi)
Mijoz ilova client vazifalar-api
Avtorizatsiya serveri authorization server Google'ning kirish serveri
Resurs serveri resource server Google API (userinfo)
  • Resurs egasi — ma'lumot kimniki bo'lsa, o'sha. U ruxsat beradi yoki rad etadi.
  • Mijoz (client) — ruxsat so'ragan dastur. E'tibor bering: bu brauzer emas, bizning server. OAuth tilida "mijoz" — Google'ga nisbatan mijoz.
  • Avtorizatsiya serveri — foydalanuvchini tanib, token beradigan joy. OIDC tilida uni ko'pincha provayder (OpenID Provider) deyishadi.
  • Resurs serveri — token bilan himoyalangan API.

2.2 Mijoz turlari

Mijoz ikki xil bo'ladi va bu xavfsizlikka bevosita ta'sir qiladi:

  • Konfidensial mijoz (confidential client) — sirni saqlay oladi. Bizning API serveri: client_secret .env da turadi, foydalanuvchi uni ko'rmaydi.
  • Ochiq mijoz (public client) — sir saqlay olmaydi. SPA yoki mobil ilova: kodi foydalanuvchining qurilmasida, ichidagi har qanday "sir" o'qib olinadi.

Tashqi login va SMS kod bilan kirish UI darsida React tomonini ko'rgansiz. U yerda code_verifier ni brauzer saqlagan edi. 28-qismda biz boshqa, xavfsizroq yo'lni tanlaymiz: hamma narsani server qiladi, brauzer faqat yo'naltiriladi. Bu naqshni BFF (Backend for Frontend — frontend uchun backend) deyishadi.

Tekshirib ko'ring: Telegram bot ichidagi Mini App — konfidensial mijozmi yoki ochiqmi? Uning backend'i-chi?

Javob

Mini App sahifasi foydalanuvchining telefonida ishlaydi — u ochiq mijoz: unga sir yozib bo'lmaydi. Uning backend'i (sizning serveringiz) esa konfidensial: bot tokeni va boshqa sirlar faqat serverda. Shuning uchun imzo va tokenlarni doim backend tekshiradi.

3. OAuth va OpenID Connect

3.1 Ikki xil token

OAuth javobida access token keladi. OIDC esa yana bittasini qo'shadi — id_token:

access token id_token
Kim uchun resurs serveri (API) mijoz (bizning server)
Nima deydi "bu tokenga X qilish mumkin" "bu foydalanuvchi — Y, hozirgina kirdi"
Format istalgan (ko'pincha tasodifiy satr) doim JWT, imzolangan
Mijoz ichini o'qiydimi yo'q ha, tekshiradi

Eng ko'p uchraydigan xato — access token'ni "kimlik isboti" deb ishlatish. Access token resurs serveriga atalgan. U boshqa ilova uchun berilgan bo'lishi ham mumkin. Kimlikni faqat sizga atalgan (aud maydonida sizning client_id) id_token isbotlaydi.

3.2 Scope va claim

Scope (ruxsat doirasi) — mijoz nimani so'rayotgani. OIDC uchun asosiy qiymatlar:

  • openid — majburiy; usiz bu oddiy OAuth so'rovi, id_token yo'q;
  • email — email va email_verified;
  • profile — name, picture va boshqalar.

Claim (da'vo) — token ichidagi bitta maydon: sub, email, exp. id_token'ning majburiy claim'lari (JWT anatomiyasi darsidan tanish shakl):

Claim Ma'nosi
iss kim berdi (provayder manzili)
sub foydalanuvchining provayderdagi o'zgarmas identifikatori
aud kimga atalgan (sizning client_id)
exp, iat tugash va berilish vaqti
nonce siz yuborgan tasodifiy qiymat

sub — eng muhim maydon. Email o'zgarishi mumkin, sub esa o'zgarmaydi. Keyingi darsda bazada aynan sub bo'yicha bog'laymiz.

4. Authorization code + PKCE oqimi

4.1 Diagramma

Kanon tanlagan oqim (server — mijoz, brauzer — faqat yo'naltiriladi):

sequenceDiagram
  participant B as Brauzer
  participant A as vazifalar-api
  participant P as Provayder
  B->>A: GET /v1/auth/oidc/google
  A->>A: state, nonce, verifier yaratadi
  A-->>B: 303 → provayder, cookie
  B->>P: /auth?client_id&redirect_uri&state&nonce&code_challenge
  P->>P: login va rozilik
  P-->>B: 303 → redirect_uri?code&state&iss
  B->>A: GET /qaytish?code&state (+ cookie)
  A->>P: POST /token: code + verifier + secret
  P-->>A: id_token, access_token
  A->>A: id_token tekshiruvi, foydalanuvchi
  A-->>B: 303 → ilova, sessiya cookie

Diagrammaga qarang: tokenlar faqat API va provayder orasida yuradi. Brauzer ikki narsani ko'radi — bir martalik code va oxirida o'zimizning sessiya cookie'mizni (Session-based auth). Provayder tokeni brauzerga umuman kelmaydi.

4.2 Avtorizatsiya so'rovi

Provayderga yo'naltirish manzilida sakkizta parametr bor. Kanon tajribasida (lokal soxta provayder) openid-client kutubxonasi yasagan URL'ning parametr nomlari:

text
redirect_uri, scope, code_challenge, code_challenge_method, state, nonce, client_id, response_type
Parametr Vazifasi
response_type=code "menga kod bering" (token emas)
client_id qaysi ilova so'rayapti
redirect_uri kod qayerga qaytsin — oldindan ro'yxatdagi bilan aynan bir xil
scope openid email profile
state login CSRF'dan himoya
nonce id_token bilan takroriy hujumdan himoya
code_challenge PKCE "qulfi"
code_challenge_method S256 — SHA-256 bilan

4.3 PKCE eslatma

PKCE (RFC 7636, "piksi" deb o'qiladi) mexanizmini React darsida brauzerda hisoblagansiz. Qisqa takror: mijoz tasodifiy verifier yaratadi va provayderga faqat uning SHA-256 hash'ini — challenge — yuboradi. Kodni tokenga almashtirishda verifier'ning o'zi beriladi. RFC 7636 ning B ilovasidagi rasmiy namuna bilan tekshiramiz:

js
const b64url = (baytlar) =>
  btoa(String.fromCharCode(...baytlar))
    .replaceAll("+", "-")
    .replaceAll("/", "_")
    .replace(/=+$/, "");

async function challengeHisobla(verifier) {
  const baytlar = new TextEncoder().encode(verifier);
  const hash = await crypto.subtle.digest("SHA-256", baytlar);
  return b64url(new Uint8Array(hash));
}

const verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
const kutilgan = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM";
const challenge = await challengeHisobla(verifier);
console.log(challenge);
console.log(challenge === kutilgan);
console.log(await challengeHisobla("boshqa-verifier") === challenge);

Konsolda:

text
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
true
false

Ikkinchi qator — natijamiz standartdagi qiymatga teng. Uchinchisi — boshqa verifier boshqa challenge beradi. Provayder aynan shu solishtiruvni qiladi.

Kanon serverda nega PKCE kerak, axir bizda client_secret bor? RFC 9700 (OAuth 2.0 xavfsizlik bo'yicha eng yaxshi amaliyot, 2025-yil yanvar) ochiq mijozlar uchun PKCE'ni majburiy, konfidensial mijozlar uchun tavsiya qiladi. Sababi — kodni joylash (authorization code injection): hujumchi o'zi olgan kodni sizning brauzeringiz orqali sizning serveringizga "joylab" yuborishi mumkin. PKCE'da kod faqat uni so'ragan oqimning verifier'i bilan ishlaydi.

4.4 Kodni tokenga almashtirish

Qaytgan code bilan server provayderning token endpoint'iga POST yuboradi: code, code_verifier, redirect_uri va client_secret. Javobni kanon tajribasi o'lchadi:

text
  token javobi: token_type=bearer, expires_in=3600, access_token 43 belgi (opaque), id_token 615 belgi, refresh yo'q
  id_token header: {"alg":"RS256","kid":"keystore-CHANGE-ME"}; JWKS kid'lari: keystore-CHANGE-ME
  • access token — 43 belgili tasodifiy satr (opaque — ichi o'qilmaydi);
  • id_token — JWT, RS256 bilan imzolangan; kid — qaysi kalit bilan imzolangani;
  • refresh token yo'q — biz uni so'ramadik (bizga Google API'ga keyinroq kirish kerak emas).

keystore-CHANGE-ME — lokal provayderning namoyish kaliti nomi. Haqiqiy provayderda kalitlar o'z nomlari bilan va aylantirib turiladi.

5. id_token'ni tekshirish

5.1 Nimani tekshiramiz

id_token kelganda mijoz uni ko'r-ko'rona qabul qilmaydi. OIDC Core standarti bo'yicha asosiy tekshiruvlar:

  1. imzo — provayderning ochiq kaliti bilan (JWKS ro'yxatidan, kid bo'yicha);
  2. iss — kutilgan provayder;
  3. aud — bizning client_id;
  4. exp — muddati o'tmagan;
  5. nonce — biz shu oqim uchun yuborgan qiymat.

Imzo qismini openid-client qiladi (JWT anatomiyasi darsida HMAC bilan qo'lda qilgansiz). Claim'lar mantig'i esa oddiy funksiya:

js
function claimXatolari(c, kutilgan, hozir) {
  const xatolar = [];
  if (c.iss !== kutilgan.issuer) xatolar.push("iss");
  const aud = Array.isArray(c.aud) ? c.aud : [c.aud];
  if (!aud.includes(kutilgan.clientId)) xatolar.push("aud");
  if (typeof c.exp !== "number" || c.exp <= hozir) {
    xatolar.push("exp");
  }
  if (c.nonce !== kutilgan.nonce) xatolar.push("nonce");
  return xatolar;
}

const kutilgan = {
  issuer: "https://accounts.google.com",
  clientId: "vazifalar",
  nonce: "n-7Xq",
};
const hozir = 1_800_000_000;
const yaxshi = {
  iss: "https://accounts.google.com", aud: "vazifalar",
  sub: "1001", exp: hozir + 3600, nonce: "n-7Xq",
};

console.log(claimXatolari(yaxshi, kutilgan, hozir));
console.log(claimXatolari(
  { ...yaxshi, aud: "boshqa-ilova" }, kutilgan, hozir));
console.log(claimXatolari(
  { ...yaxshi, exp: hozir - 1, nonce: "eski" }, kutilgan, hozir));

Konsolda:

text
[]
[ 'aud' ]
[ 'exp', 'nonce' ]

Ikkinchi holat — eng xavflisi. Boshqa ilova uchun berilgan haqiqiy Google tokeni. Imzo to'g'ri, lekin u sizga atalmagan. Hujumchi o'z saytida Google login qo'yib, foydalanuvchilarning tokenlarini yig'ishi va sizning API'ingizga yuborishi mumkin. aud tekshiruvi buni to'xtatadi.

5.2 Kanon topilmasi: email qayerda?

Kanon tajribasida id_token ichida email yo'q chiqdi:

text
  id_token claim'lari: iss=http://127.0.0.1:4351, aud=vazifalar, sub=google-sub-1001, email=undefined, email_verified=undefined, nonce mos=true, exp-iat=3600 s
  userinfo (access_token bilan): {"sub":"google-sub-1001","email":"aziz.google@example.com","email_verified":true,"name":"Aziz Karimov"}

Sababi: oidc-provider sukut bo'yicha scope claim'larini access token berilganda id_token'ga emas, faqat userinfo endpoint'iga qo'yadi. Standart ikkalasiga ham ruxsat beradi. Google esa hujjatiga ko'ra email scope'i bo'lsa, email va email_verified ni id_token'ga qo'yadi.

Xulosa — mijoz ikkala holatga tayyor bo'lsin: id_token'da email bo'lmasa, access token bilan userinfo'dan so'rasin. Kanon kodi keyingi darsda aynan shunday qiladi. Bitta provayderda sinab, "hamma joyda shunday" deb o'ylash — integratsiyadagi eng keng tarqalgan xatolardan biri.

6. Discovery: provayderning "pasporti"

6.1 .well-known/openid-configuration

Mijoz endpoint manzillarini qo'lda yozmaydi. Har OIDC provayderida standart manzil bor: <issuer>/.well-known/openid-configuration. U JSON qaytaradi. Google'nikini o'qib ko'rdik (biz ishga tushirdik, 2026-10-28; javob uzun, kerakli maydonlarni qoldirdik):

bash
curl -s https://accounts.google.com/.well-known/openid-configuration
text
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"scopes_supported": ["openid", "email", "profile"],
"id_token_signing_alg_values_supported": ["RS256"],
"code_challenge_methods_supported": ["plain", "S256"],
Maydon Nima uchun
issuer id_token'dagi iss shunga teng bo'lishi shart
authorization_endpoint foydalanuvchi yo'naltiriladigan joy
token_endpoint kod → token
userinfo_endpoint access token bilan profil
jwks_uri imzoni tekshirish uchun ochiq kalitlar

Kanonning lokal provayderi ham xuddi shu shaklda javob berdi (/auth, /token, /me, /jwks, code_challenge_methods: ["S256"]). Shuning uchun bitta mijoz kodi Google'ga ham, lokal provayderga ham ishlaydi — faqat issuer manzili boshqa. Keyingi darsdagi testlar shunga tayanadi.

Tekshirib ko'ring: Google ro'yxatida plain ham bor. Unda code_challenge_method=plain bilan ishlasa bo'ladimi?

Javob

plain da challenge = verifier: qulf va kalit bir xil. Kodni ushlagan hujumchi avtorizatsiya URL'ida challenge'ni ham ko'rgan — demak verifier ham uning qo'lida. Himoya yo'qoladi. RFC 7636 plain ni faqat S256 texnik jihatdan imkonsiz bo'lgan mijozlar uchun qoldirgan. Doim S256.

7. Kanon tajribasi: to'liq oqim lokal provayderda

7.1 Nega lokal

Haqiqiy Google bilan sinash uchun Google Cloud hisobi, kalitlar va internet kerak. Bundan tashqari, testda Google'ga har safar kirish — sekin va beqaror. Kanon boshqa yo'lni tanladi: oidc-provider 9.12.2 — OIDC provayderining to'liq Node kutubxonasi. U 127.0.0.1:4351 da ishga tushirildi, mijoz sifatida openid-client 6.8.8 ishlatildi. Login va rozilik ekranlari avtomatik "bosildi".

Chiqishda provayderning o'z ogohlantirishlari bor:

text
oidc-provider WARNING: a quick start development-only in-memory adapter is used, you MUST change it in order to not lose all stateful provider data upon restart and to be able to share these between processes
oidc-provider WARNING: quick start development-only signing keys are used, you are expected to provide your own in the configuration "jwks" property

"Faqat ishlab chiqish uchun xotiradagi adapter va namoyish kalitlari ishlatilmoqda". Bu to'g'ri — biz o'z provayderimizni production'ga chiqarmayapmiz, faqat mijozni sinayapmiz.

7.2 Yo'naltirishlar zanjiri

Brauzer taqlidi (fetch, redirect: "manual") har qadamni yozdi. Uzun identifikatorlarni kanon … bilan almashtirgan:

text
  303 /auth → /interaction/…
  303 /interaction/Swmlc4gFS1bbqfWOK2VyzKnBoh4sDK7LI9CNBnpitru → http://127.0.0.1:4351/auth/…
  303 /auth/Swmlc4gFS1bbqfWOK2VyzKnBoh4sDK7LI9CNBnpitru → /interaction/…
  303 /interaction/ZC-H8diqhxrrE2874pTrPzTAixTzWx0H-vx6FGmjgTm → http://127.0.0.1:4351/auth/…
  303 /auth/ZC-H8diqhxrrE2874pTrPzTAixTzWx0H-vx6FGmjgTm → http://localhost:4352/qaytish?code=…&state=…&iss=http%3A%2F%2F127.0.0.1%3A4351
  callback parametrlari: code, state, iss

Ikki "interaction" — ikki ekran: birinchisi login, ikkinchisi rozilik ("vazifalar ilovasiga emailingizni berasizmi?"). Oxirgi qatorda provayder brauzerni redirect_uri ga qaytardi va uchta parametr qo'shdi. iss — RFC 9207 bo'yicha qaysi provayder javob bergani. U bir nechta provayderli ilovada "mix-up" (chalkashtirish) hujumidan himoya qiladi.

8. Hujumlar va himoyalar

8.1 Yetti ssenariy

Kanon tajribasi har himoyani ataylab buzib ko'rdi. Hammasi — o'zimizning lokal provayder va mijozimizga qarshi. Har qatorda ikki qismga qarang: birinchi : gacha — hujum nomi, undan keyin — rad etish xabari:

text
  kodni QAYTA ishlatish: rad: OAUTH_RESPONSE_BODY_ERROR (invalid_grant): grant request is invalid
  boshqa code_verifier (kod o'g'irlangan, PKCE): rad: OAUTH_RESPONSE_BODY_ERROR (invalid_grant): grant request is invalid
  state mos emas (login CSRF): rad: OAUTH_INVALID_RESPONSE: invalid response encountered
  nonce mos emas (id_token qayta o'ynatish): rad: OAUTH_JWT_CLAIM_COMPARISON_FAILED: unexpected JWT claim value encountered
  ro'yxatda yo'q redirect_uri: provayder REDIRECT QILMADI — 400, redirect_uri
  PKCE'siz so'rov: redirect_uri ga xato bilan: error=invalid_request, Authorization Server policy requires PKCE to be used for this request
  implicit (response_type=token): error=#error=unsupported_response_type&error_description=unsupported+response_type+requested&state=Aq_KhsW6BeTRbwhi1D7ZlOMb5mwcehlRYMrj5IrcD2s&iss=http%3A%2F%2F127.0.0.1%3A4351

Nima bo'ldi? Yetti hujumdan yettitasi ham rad etildi — lekin har xil joyda. Qatorma-qator:

Hujum Kim to'xtatdi Natija
kodni qayta ishlatish provayder invalid_grant — kod bir martalik
o'g'irlangan kod, boshqa verifier provayder (PKCE) invalid_grant
state mos emas mijoz (openid-client) javob rad etildi
nonce mos emas mijoz claim solishtiruvi yiqildi
begona redirect_uri provayder yo'naltirish yo'q, 400 sahifa
PKCE'siz provayder siyosati requires PKCE
implicit oqim provayder unsupported_response_type

Ikki xil himoyachi borligiga e'tibor bering. Provayder kodni, verifier'ni va manzilni tekshiradi. Mijoz esa state va nonce ni — ularni faqat u biladi. Mijoz bu tekshiruvlarni qilmasa, provayder uni qutqara olmaydi.

8.2 state va login CSRF

Login CSRF — teskari hujum. Hujumchi o'z Google hisobi bilan oqimni boshlaydi va qaytish havolasini (?code=…) sizga yuboradi. Siz bossangiz, server sizni hujumchining hisobiga kiritadi. Keyin siz u yerga vazifalar, telefon raqam, karta ma'lumotini yozasiz — hujumchi hammasini o'qiydi.

Himoya: state sizning brauzeringizga bog'langan bo'ladi (kanonda imzolangan cookie'da). Begona havoladagi state cookie'dagi bilan mos kelmaydi — rad. Kanon jadvalidagi uchinchi qator aynan shu.

8.3 redirect_uri — aniq moslik

RFC 9700 talabi: provayder redirect_uri ni ro'yxatdagi bilan aniq satr sifatida solishtiradi. "Boshi mos kelsa yetadi" degan yumshoq qoida bilan hujumchi https://example.com/qaytish/../yovuz kabi manzil orqali kodni o'ziga olib ketgan voqealar bo'lgan. Kanon tajribasida provayder begona manzilga yo'naltirmadi ham. Bu muhim: yo'naltirsa, xato bilan bo'lsa ham, state begona saytga ketgan bo'lardi.

8.4 Implicit va parol oqimlari eskirgan

OAuth 2.0 ning asl matnida yana ikki oqim bor edi:

  • Implicit (response_type=token) — token to'g'ridan-to'g'ri brauzer manziliga (#access_token=…) keladi. U brauzer tarixiga, Referer sarlavhasiga, kengaytmalarga sizadi. RFC 9700: mijozlar uni ishlatmasligi kerak (SHOULD NOT).
  • Resource owner password — ilova foydalanuvchining parolini to'g'ridan-to'g'ri so'raydi. RFC 9700: ishlatilmaydi (MUST NOT). Bu aynan «Parolni so'rash davri» bo'limidagi holat.

Google discovery'sida token hali ham ro'yxatda turibdi — eski ilovalar uchun. "Provayder ruxsat beradi" degani "xavfsiz" degani emas. Kanonning lokal provayderi esa uni umuman o'chirgan — jadvaldagi oxirgi qator.

Tekshirib ko'ring: Hujumchi foydalanuvchi brauzeridan code ni ham, state ni ham ko'chirib oldi (masalan, kengaytma orqali). PKCE bo'lmaganda nima bo'lardi, PKCE bilan-chi?

Javob

PKCE'siz: hujumchi code ni o'z mijozi orqali tokenga almashtirishga urinadi. Agar mijoz konfidensial bo'lsa, unga client_secret ham kerak — bu qiyin. Lekin u kodni sizning serveringizga, o'z brauzeridan yuborishi mumkin (kodni joylash hujumi). PKCE bilan esa kodga faqat uni boshlagan oqimning verifier'i mos keladi. Verifier sizning serveringiz cookie'sida, hujumchida emas — invalid_grant.

9. Hujumchi nigohi

Hujum Nima uchun ishlaydi Himoya Qayerda sinaldi
login CSRF (begona code) state tekshirilmaydi state brauzerga bog'langan cookie'da kanon tajribasi: OAUTH_INVALID_RESPONSE
o'g'irlangan kod kod yolg'iz o'zi yetarli PKCE (S256) kanon tajribasi: invalid_grant
boshqa ilovaning tokeni aud tekshirilmaydi aud == client_id «id_token'ni tekshirish» misoli
id_token bilan takroriy hujum nonce tekshirilmaydi har oqimga yangi nonce kanon tajribasi: claim solishtiruvi yiqildi
kodni begona saytga yo'naltirish redirect_uri yumshoq solishtiriladi aniq satr, ro'yxat kanon tajribasi: 400, yo'naltirish yo'q
token brauzer tarixida implicit oqim faqat code kanon tajribasi: unsupported_response_type
email bilan hisobni egallash email — kimlik deb olinadi sub + email_verified keyingi darsdagi testlar

Oxirgi qator keyingi darsning markazida turadi. Email o'zgaradi, boshqa provayderda tasdiqlanmagan bo'lishi mumkin. Hisob (provayder, sub) bo'yicha bog'lanadi.

10. Ko'p uchraydigan xatolar

Xato Nega xavfli To'g'risi
access token'ni kimlik deb olish boshqa ilova tokeni ham o'tadi id_token + aud
state ni yaratib, tekshirmaslik login CSRF qaytishda solishtirish
hamma oqimga bitta nonce takroriy hujum har oqimga yangi
code_challenge_method=plain challenge = verifier S256
endpoint'larni qo'lda yozish provayder o'zgartirsa sinadi discovery
"id_token'da email doim bor" provayderga bog'liq userinfo zaxirasi
email bo'yicha foydalanuvchini topish email o'zgaradi sub
token'ni frontend'ga berish XSS bilan o'g'irlanadi BFF, sessiya cookie

11. Mashqlar

Mashqlar kurs/mashqlar/28/14-oauth-oidc/ da.

1-mashq (oson): rolni toping

"Malika vazifalar ga Telegram orqali kiradi. vazifalar-api Telegram'dan foydalanuvchi ismini oladi." Har ishtirokchining OAuth rolini yozing: Malika, vazifalar-api, Telegram'ning kirish serveri. Keyin: vazifalar-api konfidensial mijozmi yoki ochiq?

Yechim
  • Malika — resurs egasi (resource owner): ma'lumot uniki, ruxsatni u beradi.
  • vazifalar-api — mijoz (client).
  • Telegram'ning kirish serveri — avtorizatsiya serveri (provayder). Bu yerda u resurs serveri ham: ism to'g'ridan id_token ichida keladi.

vazifalar-api — konfidensial mijoz: u serverda ishlaydi va sirni .env da saqlay oladi.

2-mashq (o'rta): avtorizatsiya URL'i

authUrl(endpoint, p) funksiyasini yozing. U URL va url.searchParams.set yordamida provayder manzilini yasaydi: response_type=code, client_id, redirect_uri, scope, state, nonce, code_challenge, code_challenge_method=S256. Agar p da state, nonce yoki codeChallenge bo'lmasa — Error tashlasin ("state yo'q"). Ikki marta sinang: to'liq va nonce siz. Ishora: URL va searchParams — Qo'lda routing va query parametrlar darsidan; xatoni try/catch bilan ushlang (try/catch).

Yechim
js
function authUrl(endpoint, p) {
  for (const k of ["state", "nonce", "codeChallenge"]) {
    if (!p[k]) throw new Error(`${k} yo'q`);
  }
  const url = new URL(endpoint);
  const q = url.searchParams;
  q.set("response_type", "code");
  q.set("client_id", p.clientId);
  q.set("redirect_uri", p.redirectUri);
  q.set("scope", "openid email profile");
  q.set("state", p.state);
  q.set("nonce", p.nonce);
  q.set("code_challenge", p.codeChallenge);
  q.set("code_challenge_method", "S256");
  return url;
}

const p = {
  clientId: "vazifalar",
  redirectUri: "https://example.com/v1/auth/oidc/google/qaytish",
  state: "s-1",
  nonce: "n-1",
  codeChallenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
};

const GOOGLE = "https://accounts.google.com/o/oauth2/v2/auth";
const url = authUrl(GOOGLE, p);
console.log(url.host + url.pathname);
console.log([...url.searchParams.keys()].join(", "));
console.log(url.searchParams.get("scope"));

try {
  authUrl(GOOGLE, { ...p, nonce: "" });
} catch (e) {
  console.log(e.message);
}

Konsolda:

text
accounts.google.com/o/oauth2/v2/auth
response_type, client_id, redirect_uri, scope, state, nonce, code_challenge, code_challenge_method
openid email profile
nonce yo'q

searchParams.set qiymatlarni o'zi kodlaydi: redirect_uri dagi / va : URL'da %2F va %3A bo'lib chiqadi. Qo'lda "?a=" + qiymat deb yig'sangiz, bo'sh joy yoki & bor qiymat URL'ni buzadi. nonce siz URL yasashdan bosh tortish — xavfsiz sukut: unutilgan himoya jim o'tib ketmaydi.

3-mashq (qiyin): qaytish manzilini tekshirish

Provayder brauzerni redirect_uri?code=…&state=…&iss=… ga qaytaradi, xato bo'lsa — ?error=…&state=…. qaytishTekshir(manzil, saqlangan) funksiyasini yozing (saqlangan = { state, issuer }). U obyekt qaytarsin: { ok: true, code } yoki { ok: false, sabab }. Tartib: (1) error bor — sabab shu xato kodi; (2) state mos emas — "state"; (3) iss bor va mos emas — "iss"; (4) code yo'q — "code". To'rtta holatda sinang. Ishora: new URL(manzil).searchParams.get("state") — null qaytarishi mumkin.

Yechim
js
function qaytishTekshir(manzil, saqlangan) {
  const q = new URL(manzil).searchParams;
  const xato = q.get("error");
  if (xato !== null) return { ok: false, sabab: xato };
  if (q.get("state") !== saqlangan.state) {
    return { ok: false, sabab: "state" };
  }
  const iss = q.get("iss");
  if (iss !== null && iss !== saqlangan.issuer) {
    return { ok: false, sabab: "iss" };
  }
  const code = q.get("code");
  if (!code) return { ok: false, sabab: "code" };
  return { ok: true, code };
}

const asos = "https://example.com/v1/auth/oidc/google/qaytish";
const saqlangan = {
  state: "s-1",
  issuer: "https://accounts.google.com",
};
const iss = encodeURIComponent(saqlangan.issuer);

const MANZILLAR = [
  `${asos}?code=k-42&state=s-1&iss=${iss}`,
  `${asos}?code=k-42&state=hujumchi&iss=${iss}`,
  `${asos}?error=access_denied&state=s-1`,
  `${asos}?code=k-42&state=s-1&iss=https%3A%2F%2Fyovuz.example`,
];
for (const m of MANZILLAR) {
  console.log(qaytishTekshir(m, saqlangan));
}

Konsolda:

text
{ ok: true, code: 'k-42' }
{ ok: false, sabab: 'state' }
{ ok: false, sabab: 'access_denied' }
{ ok: false, sabab: 'iss' }
  • Birinchi — hammasi joyida, kod tokenga almashtirishga tayyor.
  • Ikkinchi — login CSRF: kod haqiqiy, lekin oqimni bu brauzer boshlamagan.
  • Uchinchi — foydalanuvchi rozilik ekranida "Bekor qilish" ni bosdi. Bu hujum emas, oddiy holat — foydalanuvchiga tushunarli xabar ko'rsating.
  • To'rtinchi — mix-up: javob boshqa provayder nomidan keldi.

Real kodda bu ishni openid-client qiladi, lekin tartibni bilish xato xabarlarini o'qishga yordam beradi.

12. Loyiha qadami: tajriba, kod — keyingi darsda

Bu darsda vazifalar-api o'zgarmaydi. Kanon faqat tajriba o'tkazdi: lokal provayder, mijoz va yetti hujum. Uning natijalari keyingi darsdagi kodning asosiga aylanadi:

  • openid-client 6.8.8 — production bog'liqligi bo'ladi;
  • oidc-provider 9.12.2 — faqat test uchun (dev bog'liqlik), Google o'rniga;
  • "email id_token'da yo'q" topilmasi — userinfo zaxirasi;
  • state, nonce va verifier — imzolangan qisqa muddatli cookie'da.

Bir texnik tafsilot. openid-client ning jose bog'liqligi 6.2.13 versiyasi kanon tekshiruvi kunida 7 kundan yangi edi. Kanon qoidasi bo'yicha yangi chiqqan paket darhol olinmaydi: ta'minot zanjiri hujumi bo'lsa, odatda shu kunlarda aniqlanadi. Shuning uchun package.json da "overrides": { "jose": "6.2.12" } qo'yildi. Buni keyingi darsda package.json da ko'rasiz.

13. Real ishda

  • "Google/Apple/Telegram bilan kirish" tugmalarining hammasi shu oqimda ishlaydi. Apple, Microsoft, GitLab — standart OIDC; ba'zi provayderlar (GitHub) faqat OAuth beradi va kimlikni API orqali olishni talab qiladi.
  • Korporativ SSO (yagona kirish) — xodim bitta hisob bilan o'nlab ichki tizimga kiradi. Keycloak, Okta, Microsoft Entra ID — OIDC provayderlari. Auth kutubxonalari va xizmatlari darsida tanlovni ko'ramiz.
  • Mobil ilova — ochiq mijoz: PKCE majburiy, client_secret yo'q, tizim brauzeri orqali (ichki WebView emas).
  • Intervyuda: "OAuth va OIDC farqi?", "PKCE nima uchun?", "state va nonce farqi?", "Nega implicit flow ishlatilmaydi?", "id_token'da nimani tekshirasiz?".

Xulosa

  • OAuth 2.0 — ruxsat (cheklangan token), OpenID Connect — kimlik (imzolangan id_token).
  • To'rt rol: resurs egasi, mijoz, avtorizatsiya serveri, resurs serveri; bizning API — konfidensial mijoz.
  • Yagona to'g'ri oqim — authorization code + PKCE (S256); implicit va parol oqimlari eskirgan (RFC 9700).
  • state — login CSRF, nonce — takroriy hujum, PKCE — o'g'irlangan kod; redirect_uri — aniq moslik.
  • id_token: imzo, iss, aud, exp, nonce; foydalanuvchi sub bo'yicha aniqlanadi, email userinfo'da bo'lishi ham mumkin.
  • Discovery hujjati endpoint'larni beradi — bitta mijoz kodi har OIDC provayderga mos.

Keyingi dars: Google bilan kirish — shu oqimni vazifalar-api ga qo'shamiz: imzolangan holat cookie'si, tashqi_hisoblar jadvali, hisoblarni bog'lash va pre-hijacking himoyasi, lokal provayder bilan testlar.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
OAuth 2.0 va OpenID Connect: authorization code, PKCE, state, nonce va idtoken — IlmHamroh