Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. passport-google-oauth20
- 2.1 Strategiya
- 2.2 Biz ishga tushirdik
- 2.3 validate() ichida nima qilinadi
- 3. Telegram imzosi — Nest guard'ida
- 3.1 Tekshiruv
- 3.2 Guard sifatida
- 4. Tayyor yechimlar: Better Auth va OneID
- 4.1 Better Auth Nest'da
- 4.2 OneID: nostandart provayder
- 4.3 Tanlov: strategiya, kutubxona yoki o'z kod
- 5. Kanon: auth controller'lari va ikki ochiq xato
- 5.1 28-qism routeri → Nest controller
- 5.2 Ochiq xato 3: email_verified satr bo'lib keladi
- 5.3 Ochiq xato 2: OTP — kod emas, hisob bo'yicha chegara
- 6. Hujumchi nigohi
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): bog'lash qarori
- 3-mashq (qiyin): hisoblagichni nollaydigan xato
- 9. Loyiha qadami: auth controller'lari
- 9.1 Fayllar
- 9.2 Tekshiruv: o'n bir kutilgan xato
- 10. Real ishda
- Xulosa
- Manbalar
OAuth NestJS'da: Google bilan kirish, Telegram imzosi, Better Auth va OneID
Qisqacha: Nest'da tashqi kirish ikki yo'l bilan yoziladi. Birinchisi — Passport strategiyasi:
passport-google-oauth20bilanAuthGuard("google")foydalanuvchini Google'ga 302 bilan yo'naltiradi, qaytishdavalidate(profile)hisobni topadi. Ikkinchisi — tayyor auth kutubxonasi: Better Auth o'z Nest moduli bilan global guard, sessiya va provayderlarni beradi.vazifalar-api28-qismdagi yo'lda qoladi: Google —openid-clientbilan to'g'ridan (OIDC), Telegram — o'z imzo tekshiruvi; endi ular Nest controller'larida. Ko'chishda 28-qismdan qolgan ikki xato tuzatildi: OTP kodlarini hisob bo'yicha cheklash va Google'ningemail_verified: "true"satri.
Bu darsda
passport-google-oauth20strategiyasini yozasiz va yo'naltirish manzilidagi har parametrni (state, PKCE,scope) o'qiysiz.- Telegram Login imzosini tekshirishni Nest guard'iga qanday o'rashni bilasiz.
- Better Auth'ning Nest integratsiyasi nima berishini va nima olib qo'yishini baholaysiz.
- OneID kabi nostandart provayder uchun strategiya qanday quriladi — tushunasiz.
vazifalar-apiga auth controller'larini qo'shasiz va ikki ochiq xatoni test bilan tuzatasiz.
Oldin bilishingiz kerak: CASL bilan policy-based avtorizatsiya, OAuth 2.0 va OpenID Connect nazariyasi, Google bilan kirish, Telegram Login va Mini App initData tekshiruvi, OneID orqali kirish, Auth kutubxonalari va xizmatlari.
Oldingi uch darsda "kim?" va "nima qila oladi?" qatlamlari Nest'ga ko'chdi. Endi "qayerdan kirdi?" savoli: parol, SMS, Google, Telegram, passkey. Mantiq 28-qismda yozilgan — bu darsda uni Nest'ning standart usuli bilan solishtiramiz va controller'larga joylaymiz.
1. Nega bu kerak?
Google bilan kirish darsida OIDC oqimini qo'lda yozgandik: openid-client, PKCE, state cookie'si, id_token tekshiruvi. Har qadam ko'z oldimizda edi.
Nest loyihalarida bu oqim ko'pincha bitta strategiya faylida yashaydi: GoogleStrategy extends PassportStrategy(Strategy, "google"). Qadamlar kutubxona ichida. Kod qisqa, lekin nima bo'layotganini bilmasangiz — xato ham ichkarida yashirinadi. Ayniqsa bitta xato: email_verified ni tekshirmasdan hisobni bog'lash. 28-qismdagi pre-hijacking hujumi aynan shundan boshlanadi.
O'xshatish: mehmonxona qabulxonasi. Passport strategiyasi — tayyor "Google pasporti" tekshiruvchisi: pasportni oladi, haqiqiyligini tekshiradi. Lekin "bu odam qaysi xonada yashaydi" degan savolga baribir qabulxona (sizning validate() ingiz) javob beradi. Noto'g'ri xona bersangiz — tekshiruvchining aybi yo'q.
2. passport-google-oauth20
2.1 Strategiya
Demo loyihada passport-google-oauth20@2.0.0 va uning turlarini o'rnatdik. Google'ga haqiqiy so'rov yubormaymiz — faqat Nest qayerga yo'naltirishini ko'ramiz. Client ID va sir — namuna:
@Injectable()
class GoogleStrategy extends PassportStrategy(Strategy, "google") {
constructor() {
super({
clientID: "demo-client-id.apps.googleusercontent.com",
clientSecret: "demo-sir",
callbackURL: "http://localhost:3000/auth/google/qaytish",
scope: ["openid", "email", "profile"],
state: true, // CSRF himoyasi (sessiya kerak)
pkce: true,
});
}
// Google kodni tokenga almashtirgach chaqiriladi
validate(_a: string, _r: string, p: Profile) {
const email = p.emails?.[0];
return { googleId: p.id, email: email?.value,
tasdiqlangan: email?.verified };
}
}
@Controller("auth/google")
class GoogleController {
@Get()
@UseGuards(AuthGuard("google"))
boshla(): void { /* guard Google'ga 302 qiladi */ }
@Get("qaytish")
@UseGuards(AuthGuard("google"))
qaytish(@Req() req: Request) { return req.user; }
}Ikki yo'lda bir xil guard turibdi, lekin ishi har xil:
/auth/google— so'rovdacodeyo'q. Guard Google'ga yo'naltiradi, handler umuman ishlamaydi (shuning uchun bo'sh)./auth/google/qaytish— Googlecodebilan qaytaradi. Guard kodni tokenga almashtiradi, profilni oladi vavalidate()ni chaqiradi. Natija —req.user.
sequenceDiagram
participant B as Brauzer
participant N as Nest (AuthGuard google)
participant G as Google
B->>N: GET /auth/google
N->>B: 302 + state, code_challenge
B->>G: kirish va rozilik
G->>B: 302 /qaytish?code&state
B->>N: GET /auth/google/qaytish
N->>G: code + code_verifier
G->>N: tokenlar, profil
N->>N: validate(profile) → req.userDiagrammada guard ikki marta ishlaydi: tepada — yo'naltiradi, pastda — kodni almashtiradi. Brauzer Google tokenini hech qachon ko'rmaydi: almashtirish server bilan Google o'rtasida.
validate() ning argumentlari: access token, refresh token va profil. Bizga profil kerak. Email bilan birga verified belgisi ham keladi — undan keyinroq foydalanamiz.
2.2 Biz ishga tushirdik
state va PKCE uchun Passport sessiya so'raydi (u code_verifier va state ni shu yerda eslab qoladi). Demoda har so'rovga bo'sh obyekt qo'ydik — faqat yo'naltirishni ko'rish uchun. Haqiqiy loyihada bu express-session yoki imzolangan cookie bo'ladi. Natija (NestJS 12.2.0, @nestjs/passport 12.0.0):
GET /auth/google → 302
Location: https://accounts.google.com/o/oauth2/v2/auth
response_type = code
redirect_uri = http://localhost:3000/auth/google/qaytish
scope = openid email profile
code_challenge = <43 belgi>
code_challenge_method = S256
state = <24 belgi>
client_id = demo-client-id.apps.googleusercontent.com
GET /auth/google/qaytish?code=soxta&state=x → 401 {"message":"Unauthorized","statusCode":401}state va code_challenge tasodifiy — uzunligini qoldirdik. Har qatorni OAuth 2.0 nazariyasi darsidan taniysiz:
response_type=code— Authorization Code oqimi.code_challenge+S256— PKCE:code_verifierning SHA-256 hash'i (43 belgi base64url).state— login CSRF himoyasi: qaytishda xuddi shu qiymat kelishi kerak.
Oxirgi qatorga qarang: soxta state bilan qaytish — 401, Google'ga murojaat ham qilinmadi. Strategiya sessiyadagi state ni topa olmadi va to'xtadi. 28-qismdagi testlar ham shu holatni tekshiradi: "holat cookie'si yo'q, imzosi buzilgan yoki boshqa brauzerniki — oidc-xato".
2.3 validate() ichida nima qilinadi
Demo profilni qaytardi. Haqiqiy validate() — eng mas'uliyatli joy. U uch holatni ajratadi (Google bilan kirish darsidagi qoidalar):
| Holat | Qaror |
|---|---|
Shu sub bilan hisob bor |
o'sha hisob (email o'zgargan bo'lsa ham) |
| Shu email bilan tasdiqlangan hisob bor va Google emaili tasdiqlangan | bog'lash |
| Shu email bilan tasdiqlanmagan hisob bor | hujumchi oldindan ochgan bo'lishi mumkin: uning paroli o'chiriladi, sessiyalari bekor |
| Google emaili tasdiqlanmagan | rad etish |
Hisob sub bo'yicha topiladi, email bo'yicha emas: email o'zgaradi, sub — yo'q. Bu jadval 28-qismdagi tashqiKirish() funksiyasida yozilgan va o'zgarmagan.
Tekshirib ko'ring: Nega
validate()hisobni email bo'yicha emas,subbo'yicha qidiradi?
Javob
Email — o'zgaruvchan va qayta beriladigan narsa. Foydalanuvchi Google'da emailini o'zgartirishi, kompaniya esa eski xodimning manzilini boshqasiga berishi mumkin. sub — provayder ichida bu odamga berilgan doimiy raqam. Email bo'yicha qidirsangiz, emailni olgan begona odam eski egasining hisobiga kiradi.
3. Telegram imzosi — Nest guard'ida
3.1 Tekshiruv
Telegram Login Widget — OAuth emas. Brauzer foydalanuvchi ma'lumotini imzo (hash) bilan to'g'ridan serverga yuboradi. Server imzoni bot tokeni bilan tekshiradi (Telegram Login darsi): sir — SHA256(bot_token), imzo — HMAC-SHA256(sir, tekshiruv_satri). Tekshiruv satri — hash dan boshqa maydonlar, alifbo tartibida, kalit=qiymat, \n bilan.
Web Crypto bilan model (brauzerda ishlaydi). Token — soxta, faqat shu blok uchun:
const hex = (b) => [...new Uint8Array(b)]
.map((x) => x.toString(16).padStart(2, "0")).join("");
const satr = (m) => Object.keys(m).filter((k) => k !== "hash").sort()
.map((k) => `${k}=${m[k]}`).join("\n");
async function imzo(maydon, botToken) {
const enc = new TextEncoder();
const sir = await crypto.subtle.digest("SHA-256",
enc.encode(botToken));
const kalit = await crypto.subtle.importKey("raw", sir,
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const b = await crypto.subtle.sign("HMAC", kalit,
enc.encode(satr(maydon)));
return hex(b);
}
const TOKEN = "123456:SOXTA-token-faqat-darsda";
const m = { id: "777", first_name: "Aziz", auth_date: "1793600000" };
m.hash = await imzo(m, TOKEN);
console.log("to'g'ri:", m.hash === (await imzo(m, TOKEN)));
const soxta = { ...m, id: "1" }; // boshqa odam bo'lib ko'rinish
const qayta = await imzo(soxta, TOKEN);
console.log("id almashtirildi:", soxta.hash === qayta);
console.log("boshqa bot:", m.hash === (await imzo(m, "999:boshqa")));Konsolda:
to'g'ri: true
id almashtirildi: false
boshqa bot: falseModelda soddalashtirgan joylar: haqiqiy kod auth_date eskirmaganini ham tekshiradi va satrlarni timingSafeEqual bilan solishtiradi (28-qismdagi widgetTekshir).
3.2 Guard sifatida
Nest uslubida bu tekshiruvni guard qilish tabiiy: imzo noto'g'ri bo'lsa, so'rov controller'gacha yetmasin.
@Injectable()
export class TelegramGuard implements CanActivate {
constructor(@Inject(TELEGRAM) private readonly tg: TgSozlama) {}
canActivate(ctx: ExecutionContext): boolean {
const req = ctx.switchToHttp().getRequest<Request>();
const n = widgetTekshir(req.body as Record<string, string>,
this.tg.botToken, this.tg.maksS);
if (n.tur !== "ok") throw new UnauthorizedException(n.sabab);
req.telegram = n.foydalanuvchi; // keyingi handler uchun
return true;
}
}Bu bizning eskiz (kanon emas). Diqqat: guard pipe'dan oldin ishlaydi (so'rov hayot sikli) — tana hali sxema bilan tekshirilmagan. Shuning uchun widgetTekshir noma'lum shakldagi obyektni xavfsiz o'qishi kerak. Kanon boshqacha qiladi: POST /v1/auth/telegram handler'i tanani avval Zod sxemasidan o'tkazadi, keyin xizmat (telegramKirish) imzoni tekshiradi. Ikkalasi ham to'g'ri, kanonniki tartib jihatidan xavfsizroq.
Ikki yondashuvni so'rov yo'li bo'yicha yonma-yon qo'yamiz:
| Qadam | Eskiz: TelegramGuard |
Kanon: handler + xizmat |
|---|---|---|
| guard | imzo tekshiruvi — tana hali xom | KirishGuard: yo'l @Ochiq() — o'tadi |
| pipe | tana sxemasi (agar bo'lsa) | Zod sxemasi (TelegramSxemasi): qiymatlar satr yoki son (satrga aylanadi), hash — 128 belgigacha satr, ortiqcha maydon yo'q |
| handler / xizmat | req.telegram ni ishlatadi |
telegramKirish(): imzo, auth_date, sessiya |
| xato | UnauthorizedException — Nest formati |
IlovaXatosi — Problem Details |
Guard varianti qulayroq ko'rinadi: imzo tekshiruvi bir joyda, handler toza. Lekin u xom tana bilan ishlashga majbur. Masalan, hujumchi hash o'rniga massiv yuborsa, widgetTekshir buni o'zi ushlashi kerak — aks holda 500. Kanon variantida bu savol yo'q: imzo tekshiruviga faqat shakli tasdiqlangan obyekt yetib keladi.
4. Tayyor yechimlar: Better Auth va OneID
4.1 Better Auth Nest'da
Auth kutubxonalari darsida Better Auth'ni o'lchagandik: 30 paket, 20 MB, ichida parol, sessiya, OAuth provayderlari va plaginlar. Uning Nest integratsiyasi — jamiyat paketi @thallesp/nestjs-better-auth, Better Auth hujjatining "NestJS Integration" sahifasida tavsiya qilingan. Hujjat bo'yicha (biz sinamadik, 2026-11-02 da o'qildi):
// main.ts — Better Auth xom tanani o'zi o'qiydi
const app = await NestFactory.create(AppModule, {
bodyParser: false, // Required for Better Auth
});
// app.module.ts — global AuthGuard: hamma yo'l yopiq
@Module({ imports: [AuthModule.forRoot({ auth })] })
export class AppModule {}
// controller — @Session(), @AllowAnonymous(), @OptionalAuth()
@Get("me")
getProfile(@Session() session: UserSession) {
return { user: session.user };
}Taniysizmi? Global guard, "yopiq sukut", ochiq yo'llar uchun dekorator — global guard darsidagi naqshning aynan o'zi, faqat tayyor holda. bodyParser: false ham tanish: vazifalar-api ham Nest'ning parser'ini o'chirgan, chunki o'z express.json limitlari bor.
Nima olasiz va nima berasiz:
| Olasiz | Berasiz |
|---|---|
| ro'yxat, login, OAuth, 2FA — tayyor | sxema kutubxonaniki (jadvallar, nomlar) |
| tez boshlash | xato javoblari formati kutubxonaniki |
| plaginlar (tashkilot, passkey) | 20 MB va 30 paket, yangilanishlarga bog'liqlik |
Yangi loyiha, kichik jamoa, vaqt kam — kuchli tanlov. vazifalar-api da esa auth allaqachon yozilgan, testlangan va Problem Details bilan gaplashadi — almashtirish faqat xavf qo'shadi.
4.2 OneID: nostandart provayder
OneID orqali kirish darsida ko'rdik: OneID OAuth g'oyasida ishlaydi, lekin standart OIDC emas — ochiq discovery yo'q, o'z grant_type qiymatlari, bitta endpoint. Shuning uchun tayyor "OIDC strategiyasi" ishlamaydi.
Passport dunyosida bunday holat uchun umumiy passport-oauth2 strategiyasi bor: manzillar, client ID va userProfile() ni o'zingiz berasiz. Yoki Passport NestJS'da darsidagi kanon tajribasi kabi passport-custom bilan butunlay o'z validate(req) ingiz. Ikkala holatda ham og'ir qism Passport emas, provayder protokolini to'g'ri bajarish — va uni faqat rasmiy hujjat bilan qilish mumkin. Biz OneID'ni sinamadik; parametrlar ulanish shartnomasi bilan beriladi.
4.3 Tanlov: strategiya, kutubxona yoki o'z kod
Endi uch yo'lni bilasiz. Qaysi birini qachon tanlash — savollar bilan:
- Nechta tashqi provayder kerak? Bitta-ikkita (Google, Telegram) — o'z kod yoki
openid-clientyetarli. O'nta (Google, GitHub, Apple, Microsoft, Facebook…) — Passport strategiyalari yoki Better Auth vaqtni tejaydi. - Auth hali yo'qmi yoki allaqachon bormi? Noldan boshlasangiz — kutubxona tez. Ishlab turgan, testlangan auth'ni almashtirish esa hech qachon "tez" emas: har oqim va har test qayta tekshiriladi.
- Javob formati va sxema kimniki? API shartnomangiz qat'iy bo'lsa (Problem Details, o'z jadval nomlari) — kutubxona bilan doim "ishqalanish" bo'ladi.
- Kim javob beradi? Xavfsizlik yangilanishlari: o'z kodingizda — sizda, kutubxonada — kutubxona mualliflarida, lekin yangilash baribir sizning ishingiz.
vazifalar-api uchun javoblar aniq: ikki provayder, auth tayyor va testlangan, shartnoma qat'iy. Shuning uchun kanon o'z kodida qoldi. Boshqa loyihada javoblar boshqacha bo'lishi mumkin — va to'g'ri qaror ham boshqacha bo'ladi.
5. Kanon: auth controller'lari va ikki ochiq xato
5.1 28-qism routeri → Nest controller
28-qismdagi marshrut.ts Nest'da AuthController bo'ldi (326 qator) — biznes qoidalari xizmat.ts da o'zgarmagan. Klass darajasida @Ochiq() (login, ro'yxat ochiq), sessiya kerak bo'lgan metodlarda @SessiyaKerak(). Google qaytishi — kanon faylidan, bo'lingan:
@Get("oidc/:provayder/qaytish")
async oidcQaytish(@Param("provayder") nom: string,
@Req() req: Request, @Res() res: Response): Promise<void> {
const p = this.d.oidc.get(nom);
if (p === undefined) throw new TopilmadiXatosi(
"Bunday provayder sozlanmagan", "provayder-yoq",
"Provayder yo'q");
try {
const k = await p.yakunla(req, res,
cookieOl(req.headers.cookie, p.cookieNomi));
const f = await this.#xizmat.tashqiKirish(k, mijoz(req));
await this.auth.sessiyaBer(req, res, f, true);
res.redirect(303, `${this.d.ilovaUrl}/`);
} catch (x) {
if (!(x instanceof IlovaXatosi)) throw x;
res.redirect(303, `${this.d.ilovaUrl}/kirish?xato=${
encodeURIComponent(x.kod ?? "xato")}`);
}
}Bu yerda @Res() — passthrough siz. Brauzer navigatsiyasi JSON emas, yo'naltirish kutadi; javobni metod o'zi yuboradi (Controller'lar). Xato bo'lsa ham foydalanuvchi bo'sh sahifa emas, frontend'dagi "kirish" sahifasini ko'radi, xato kodi query'da.
Sinov — haqiqiy Google emas, testning ichida ko'tariladigan lokal oidc-provider 9.12.2 (soxta provayder, haqiqiy OIDC protokoli). Biz testlarni o'z PostgreSQL 17.6 klasterimizda yurgizdik — 28-qismdagi 6 ta va yangi 1 ta, hammasi o'tdi.
5.2 Ochiq xato 3: email_verified satr bo'lib keladi
OIDC Core spetsifikatsiyasi email_verified ni boolean deydi. Amalda ba'zi provayderlar (eski Google userinfo, Cognito, ba'zi Azure AD sozlamalari — kanon izohi bo'yicha) uni "true" satri qilib beradi. 28-qismdagi kod faqat === true ni qabul qilardi — haqiqiy foydalanuvchi "email tasdiqlanmagan" deb rad etilardi.
Tuzatish — aniq ikki qiymat, qolgani rad (kanon faylidan, bo'lingan):
- emailTasdiqlangan: u["email_verified"] === true,
+ // faqat aniq true / "true" — tasdiqlangan; "false", "1", "yes"
+ // va boshqasi — YO'Q (xavfsiz tomonga)
+ const tasdiq = u["email_verified"];
+ … emailTasdiqlangan: tasdiq === true || tasdiq === "true", …Paketsiz model — nima o'tadi, nima yo'q:
const tasdiqlanganmi = (v) => v === true || v === "true";
for (const v of [true, "true", false, "false", "1", "yes", 1]) {
console.log(JSON.stringify(v), "→", tasdiqlanganmi(v));
}Konsolda:
true → true
"true" → true
false → false
"false" → false
"1" → false
"yes" → false
1 → falseBoolean("false") — true! Shuning uchun "satrni boolean'ga aylantirish" xavfli: "false" satri tasdiqlangan bo'lib ketardi. Biz yurgizgan test (lokal provayder satr qaytaradi):
✓ test/oidc.test.ts > email_verified="true" (satr) — tasdiqlangan deb bog'lanadi; "false" (satr) — rad 3808ms5.3 Ochiq xato 2: OTP — kod emas, hisob bo'yicha chegara
28-qismdagi SMS kirishda bitta kodga 5 urinish berilardi. Lekin hujumchi kuniga 10 ta yangi kod so'rashi mumkin — 50 urinish. Kanon hisobi: oyiga ~1 500 urinish, 6 xonali kodda bu ~0.15 % ehtimol. NIST SP 800-63B (§5.2.2) muvaffaqiyatsiz urinishlarni hisob bo'yicha cheklashni talab qiladi, 100 tadan oshmasin.
Tuzatish: oxirgi muvaffaqiyatli OTP'dan beri hisobning ketma-ket xatolari ≤ 100. Hisoblash uchun yangi jadval kerak bo'lmadi — otp_kodlar dagi urinishlar yig'indisi; parallel urinishlar chegaradan o'tib ketmasligi uchun foydalanuvchi qatori FOR UPDATE bilan qulflanadi (qulflar).
Tuzatish paytida yana bir xato topildi. Yangi SMS so'ralganda eski kodlar "bekor" qilinardi — ishlatilgan_vaqt = now() bilan. Lekin "oxirgi muvaffaqiyatli OTP" aynan ishlatilgan_vaqt bo'yicha topiladi. Ya'ni har yangi SMS hisoblagichni nollardi — hujumchi har yangi kod bilan hisobni "tozalardi". Endi bekor qilish tugash_vaqti = now() bilan, ishlatilgan_vaqt esa faqat haqiqiy muvaffaqiyatda:
- UPDATE otp_kodlar SET ishlatilgan_vaqt = now()
- WHERE telefon = $1 AND ishlatilgan_vaqt IS NULL
+ UPDATE otp_kodlar SET tugash_vaqti = now()
+ WHERE telefon = $1 AND ishlatilgan_vaqt IS NULL
+ AND tugash_vaqti > now()Tashqi javob o'zgarmadi — chegaradan keyin ham oddiy 401 ("kod noto'g'ri"), hujumchi chegara borligini bilmaydi. Jurnalda esa sabab ko'rinadi: otp-hisob-chegarasi. Biz yurgizgan test:
✓ test/otp.test.ts > hisob chegarasi: 100 ketma-ket xatodan keyin TO'G'RI kod ham 401 (jurnalda otp-hisob-chegarasi); 99 da — 200 va hisoblagich nollanadi 1134msTest nomini o'qing: ikkita holat bor. 100 xatodan keyin to'g'ri kod ham rad etiladi — chegara ishladi. 99 xatoda esa to'g'ri kod 200 oladi va hisoblagich nolga tushadi — haqiqiy foydalanuvchi jazolanmaydi.
Chegaradan keyin hisob yo'qolmaydi: parol, passkey yoki parolni tiklash orqali kirish ochiq qoladi.
Tekshirib ko'ring: Chegaradan keyin nega 429 yoki "hisob bloklandi" emas, oddiy 401 qaytadi?
Javob
429 yoki maxsus xabar hujumchiga "chegaraga yetding" deb aytadi. U shundan hisoblagich qachon nollanishini taxmin qiladi yoki boshqa hisobga o'tadi. Oddiy 401 esa "kod noto'g'ri" bilan bir xil ko'rinadi: hujumchi to'g'ri kodni topsa ham, buni bilmaydi. Haqiqiy foydalanuvchi uchun boshqa yo'llar (parol, passkey, tiklash) ochiq, sabab esa jurnalda — administrator ko'radi.
6. Hujumchi nigohi
- Tasdiqlanmagan email bilan bog'lash. Hujumchi sizning emailingiz bilan hisob ochadi, siz keyin Google bilan kirasiz — hisob "bog'lanadi", hujumchining paroli esa ishlayveradi (pre-hijacking). Himoya —
validate()dagi jadval. statesiz OAuth. Hujumchi o'zcodeini sizning brauzeringizga tiqadi — siz uning hisobiga kirasiz (login CSRF). Biz ko'rdik: soxtastate— 401.- Satrni boolean'ga aylantirish.
Boolean("false") === true. Faqat aniq qiymatlar. - OTP'ni kod bo'yicha cheklash. Yangi SMS — yangi 5 urinish. Chegara hisobda bo'lsin, hisoblagichni nollaydigan har yo'lni tekshiring.
- Guard'da tekshirilmagan tana. Guard pipe'dan oldin — tanadagi
hashmassiv yoki obyekt bo'lishi mumkin. Avval shaklni tekshiring.
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
redirect_uri_mismatch (Google sahifasida) |
callbackURL konsoldagi bilan aynan bir xil emas |
port, yo'l, http/https — belgima-belgi |
| Qaytishda doim 401 | state: true, lekin sessiya yo'q |
express-session yoki o'z holat cookie'si |
| Profilda email yo'q | scope da email yo'q |
scope: ["openid", "email", "profile"] |
| Better Auth yo'llari tanani ko'rmaydi | Nest body parser yoqiq | bodyParser: false |
| Telegram imzosi doim noto'g'ri | maydonlar alifbo tartibida emas yoki hash satrga qo'shilgan |
tartiblang, hash ni chiqarib tashlang |
8. Mashqlar
Mashqlar kurs/mashqlar/30/59-oauth/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
- Google strategiyasi paketi —
passport- - Login CSRF'dan himoya qiluvchi parametr —
- Hisob doim shu maydon bo'yicha topiladi —
- Better Auth Nest'da o'chirishni so'raydigan sozlama —
: false - OTP chegarasi kanonda nima bo'yicha —
Yechim
- google-oauth20 — Passport strategiyasi; Nest'da
PassportStrategy(Strategy, "google"). - state — tasodifiy qiymat; qaytishda mos kelmasa — 401.
- sub — provayder ichidagi doimiy identifikator; email o'zgaradi.
- bodyParser — kutubxona xom tanani o'zi o'qiydi.
- hisob — kod bo'yicha emas; NIST SP 800-63B, 100 tagacha xato.
2-mashq (o'rta): bog'lash qarori
qaror(google, mavjud) funksiyasini yozing. google — { sub, email, emailVerified }, mavjud — bazadagi hisoblar massivi { id, sub?, email, tasdiqlangan }. Natija: "kirish:<id>" (sub bo'yicha topildi), "bog'lash:<id>" (tasdiqlangan email), "egallash:<id>" (tasdiqlanmagan hisob — hujumchi paroli o'chadi), "yangi" yoki "rad" (Google emaili tasdiqlanmagan). Ishora: find va darsdagi jadval tartibi.
Yechim
const tasdiqlanganmi = (v) => v === true || v === "true";
function qaror(google, mavjud) {
const bySub = mavjud.find((h) => h.sub === google.sub);
if (bySub) return `kirish:${bySub.id}`;
if (!tasdiqlanganmi(google.emailVerified)) return "rad";
const byEmail = mavjud.find((h) => h.email === google.email);
if (!byEmail) return "yangi";
return byEmail.tasdiqlangan
? `bog'lash:${byEmail.id}` : `egallash:${byEmail.id}`;
}
const baza = [
{ id: 1, sub: "g-1001", email: "aziz@example.com",
tasdiqlangan: true },
{ id: 2, email: "dilnoza@example.com", tasdiqlangan: true },
{ id: 3, email: "qurbon@example.com", tasdiqlangan: false },
];
const g = (sub, email, emailVerified) =>
({ sub, email, emailVerified });
console.log(qaror(g("g-1001", "yangi@example.com", true), baza));
console.log(qaror(g("g-2002", "dilnoza@example.com", "true"), baza));
console.log(qaror(g("g-3003", "qurbon@example.com", true), baza));
console.log(qaror(g("g-4004", "bobur@example.com", "false"), baza));
console.log(qaror(g("g-5005", "bobur@example.com", true), baza));Konsolda:
kirish:1
bog'lash:2
egallash:3
rad
yangiBirinchi qatorda email boshqa, lekin sub mos — o'sha Aziz. Uchinchi qator — pre-hijacking himoyasi: tasdiqlanmagan hisob Google egasiga o'tadi, avvalgi parol o'chiriladi. To'rtinchisi — "false" satri rad etildi.
3-mashq (qiyin): hisoblagichni nollaydigan xato
OTP hisob chegarasining modelini yozing. Kodlar — massiv { urinish, ishlatildi, bekor }. xatolar() — oxirgi ishlatildi koddan keyingi kodlarning urinishlari yig'indisi. Ikki variantdagi yangiKod(bekorUsuli) ni yozing: "ishlatildi" (28-qismdagi xato) va "bekor" (tuzatish) — eski ishlatilmagan kodlarni shu usulda yopsin. Hujumchi 3 marta: 5 xato urinish + yangi kod. Har variantda xatolar() necha bo'ladi? Ishora: findLastIndex, slice, reduce.
Yechim
function hisob(bekorUsuli) {
const kodlar = [{ urinish: 0, ishlatildi: false, bekor: false }];
const xatolar = () => {
const i = kodlar.findLastIndex((k) => k.ishlatildi);
return kodlar.slice(i + 1).filter((k) => !k.ishlatildi)
.reduce((s, k) => s + k.urinish, 0);
};
const yangiKod = () => {
for (const k of kodlar) {
if (!k.ishlatildi && !k.bekor) k[bekorUsuli] = true;
}
kodlar.push({ urinish: 0, ishlatildi: false, bekor: false });
};
for (let n = 0; n < 3; n++) {
kodlar.at(-1).urinish += 5; // 5 ta noto'g'ri kod
yangiKod();
}
return xatolar();
}
console.log("28-qism (ishlatildi bilan bekor):", hisob("ishlatildi"));
console.log("tuzatish (bekor bilan):", hisob("bekor"));Konsolda:
28-qism (ishlatildi bilan bekor): 0
tuzatish (bekor bilan): 15Xato variantda 15 ta xato urinish izsiz yo'qoldi: har yangi kod eski kodlarni "ishlatilgan" deb belgiladi va hisoblagich noldan boshlandi. Hujumchi cheksiz davom eta olardi — chegara 100 bo'lsa ham. Tuzatishda hamma xato hisobda qoladi. Kanonda ham aynan shu bitta ustun (tugash_vaqti va ishlatilgan_vaqt) farqi hal qildi.
9. Loyiha qadami: auth controller'lari
9.1 Fayllar
| Fayl | Nima |
|---|---|
src/auth/auth.controller.ts |
yangi: /v1/auth — ro'yxat, email tasdiq, parol tiklash, kirish, chiqish, OTP, telefon, OIDC, Telegram, API kalitlar, sessiyalar |
src/auth/mfa.controller.ts |
yangi: 2FA (TOTP, zaxira kodlar) va passkey yo'llari |
src/auth/ichki.controller.ts |
yangi: xizmatlararo HMAC yo'li + ServisImzoGuard |
src/auth/otp.ts, xizmat.ts |
ochiq xato 2: hisob chegarasi, bekor qilish tugash_vaqti bilan, jurnal sababi |
src/auth/oidc.ts |
ochiq xato 3: email_verified — true yoki "true" |
Controller'lar 28-qism routerlarining yo'llari, statuslari va javoblarini aynan saqlaydi — 28-qismdagi testlar ularni o'zgarishsiz tekshiradi. Har metodda tezlik cheklovi dekoratori (@Cheklov(...)) va bot yo'llarida @CaptchaKerak() bor — ular keyingi darsda yoziladigan cheklov.guard.ts dan.
ichki.controller.ts — @Ochiq(), chunki u foydalanuvchi emas, boshqa xizmat uchun. Uni ServisImzoGuard himoya qiladi: 28-qismdagi HMAC middleware'i guard ichiga o'ralgan (vaqt oynasi, nonce takror himoyasi, xom tana baytlari ustida imzo). Biz yurgizgan test: imzosiz — 401, to'g'ri imzo — 200, o'sha nonce takror — 401.
9.2 Tekshiruv: o'n bir kutilgan xato
Shu kesimni biz kanon fayllaridan yig'ib tekshirdik (npx tsc --noEmit). Yangi xatolar:
src/auth/auth.controller.ts(19,39): error TS2307: Cannot find module './cheklov.guard.ts' or its corresponding type declarations.
src/auth/mfa.controller.ts(17,25): error TS2307: Cannot find module './cheklov.guard.ts' or its corresponding type declarations.
src/auth/mfa.controller.ts(78,57): error TS7006: Parameter 'req' implicitly has an 'any' type.
src/auth/mfa.controller.ts(78,62): error TS7006: Parameter 'a' implicitly has an 'any' type.
src/auth/mfa.controller.ts(148,67): error TS7006: Parameter 'req' implicitly has an 'any' type.Qolgan oltitasi — o'tgan darsdagilarning aynan o'zi (qoriqchi.ts ikkitasi, ilova.ts bittasi, eski testlar uchtasi). Yangi beshtasining sababi bitta: cheklov.guard.ts hali yo'q. TS7006 lar — @Cheklov({ kalit: (req, a) => … }) ichidagi parametrlar: tur NestQoida dan kelishi kerak edi. Keyingi darsda bu fayl qo'shilgach, hammasi birdan yo'qoladi.
otp.ts va oidc.ts dagi o'zgarishlar yangi xato bermadi — ular o'z turlari ichida. Hali commit qilmang.
10. Real ishda
- "Google bilan kirish" — deyarli har Nest loyihasida
passport-google-oauth20. Review'da birinchi savol: "validate()email tasdiqlanganini tekshiryaptimi va hisobni nima bo'yicha topyapti?". - Telegram — O'zbekistonda eng ko'p so'raladigan kirish usullaridan biri: Login Widget va Mini App
initData. Imzo tekshiruvi — serverda, har doim. - Better Auth, Clerk, Auth0 — startaplar va prototiplarda tez-tez; katta tizimlar ko'pincha Keycloak yoki o'z auth'iga o'tadi.
- Intervyu: "OAuth'da
statenima uchun?", "PKCE nima?", "Pre-hijacking hujumi qanday ishlaydi?", "OTP bruteforce'dan qanday himoyalanasiz?".
Xulosa
passport-google-oauth20: bir guard ikki ish — yo'naltirish (302 +state+ PKCE) va qaytish (validate(profile)); soxtastate— 401.validate()— hisobnisubbo'yicha topadi, tasdiqlanmagan email bilan bog'lamaydi.- Telegram imzosi — HMAC-SHA256; guard yoki xizmatda, lekin shakl tekshiruvidan keyin.
- Better Auth Nest moduli — tayyor global guard va sessiya; narxi — sxema, format va 20 MB.
- Kanon: OIDC va Telegram 28-qism mantig'i bilan Nest controller'larida; ochiq xato 3 (
"true"satri) va ochiq xato 2 (hisob chegarasi + hisoblagichni nollagan xato) — test bilan tuzatildi.
Keyingi dars: @nestjs/throttler — Nest'ning tezlik cheklovchisini to'liq o'rganamiz, kanon limiteri bilan o'lchaymiz va login lockout DoS'ini CAPTCHA bilan tuzatamiz.
Manbalar
- NestJS hujjati — Passport recipe (strategiyalar): https://docs.nestjs.com/recipes/passport
- passport-google-oauth20: https://www.passportjs.org/packages/passport-google-oauth20/
- Better Auth — NestJS Integration (o'qildi 2026-11-02): https://better-auth.com/docs/integrations/nestjs
- Telegram Login Widget — imzo tekshiruvi: https://core.telegram.org/widgets/login#checking-authorization
- OpenID Connect Core 1.0 — standart claim'lar (
email_verified— boolean): https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims - NIST SP 800-63B — §5.2.2 Rate Limiting (Throttling): https://pages.nist.gov/800-63-4/sp800-63b.html
- Kanon (IlmHamroh, 2026-11-01):
AuthController, ochiq xato 2–3 va ularning testlari. Demo (Google yo'naltirishi), oidc/otp/nest-auth testlari (o'z PostgreSQL 17.6 klasterimizda) va oraliqtsckesimi — biz ishga tushirdik (2026-11-02, NestJS 12.2.0, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!