Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. OAuth oqimi: kim nima qiladi
- 2.1 Authorization code
- 2.2 PKCE: kalit va qulf
- 3. Frontend qismi: yo'naltirish va qaytish
- 3.1 «Google bilan kirish» tugmasi
- 3.2 Qaytish sahifasi
- 4. Google Identity Services
- 4.1 Tayyor tugma va ID token
- 4.2 CSP va maxfiylik narxi
- 5. SMS kod bilan kirish
- 5.1 Ikki ekran
- 5.2 Qayta yuborish taymeri
- 5.3 Server nima qiladi (va frontend nimani ko'rsatadi)
- 5.4 Biz sinab ko'rdik: input-otp
- 5.5 Nega vazifalar da yo'q
- 6. Tayyor xizmatlar: Clerk, Auth0 va boshqalar
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): qaytish sahifasining qarori
- 3-mashq (qiyin): yangilanishga chidamli taymer
- 4-mashq: tajriba qadami (ixtiyoriy)
- 10. Real ishda
- Xulosa
- Manbalar
Tashqi login React'da: «Google bilan kirish», OAuth qaytish sahifasi, PKCE va SMS kod
Qisqacha: "Google bilan kirish" — OAuth oqimi: frontend foydalanuvchini Google'ga yuboradi (
stateva PKCEcode_challengebilan), Google uni qaytish sahifangizga?code=…&state=…bilan qaytaradi, frontendstateni tekshiradi vacodeni serverga beradi — tokenni server oladi. Google Identity Services esa tayyor tugma va ID token (credential) beradi — uni ham server tekshiradi. SMS kod bilan kirish — ikki ekran: telefon raqami va 6 xonali kod, qayta yuborish taymeri bilan; kodni yuborish va tekshirish (Eskiz kabi provayder) — serverda. Biz Google kaliti va SMS hisobi ochmadik: oqimlar jonli o'quv modellarida,vazifalardagi kirish esa 28-qismgacha soxta.
Bu darsda
- OAuth "authorization code" oqimini chizasiz va frontend'ning unda nima qilishini (va nima qilmasligini) bilasiz.
- PKCE
code_verifiervacode_challengeni Web Crypto bilan yasaysiz,statebilan CSRF'dan himoyalanasiz. - Qaytish (callback) sahifasini yozasiz:
code,state,errorva qayta yo'naltirish. - Google Identity Services tugmasini hujjat bo'yicha ulaysiz va uning CSP talabini bilasiz.
- SMS kod ekranlarini quraysiz: raqam, kod, qayta yuborish taymeri, xato holatlari; Clerk/Auth0 qachon kerakligini aytasiz.
Oldin bilishingiz kerak: React'da auth holati, Maxsus inputlar: telefon va OTP, Himoyalangan marshrutlar, localStorage va sessionStorage, Telegram Mini Apps II.
1. Nega bu kerak?
O'tgan darsda Telegram ichidagi mehmonni initData imzosi bilan tanidik. «Bahor» saytiga esa Telegram'siz ham kirishadi. Jasur aka parolli ro'yxatdan qochadi: "Mehmonlar parolni unutadi. Google bilan bir bosishda yoki telefon raqami bilan kirsin — SMS kod bilan".
Ikkalasi ham tashqi login: foydalanuvchi kimligini siz emas, boshqa tomon tasdiqlaydi — Google yoki mobil operator (SMS orqali). Sizning ilovangiz parol saqlamaydi.
Frontend tomonida bu ikki xil ekran va bitta qoida:
- Google — qayta yo'naltirish (redirect) va qaytish sahifasi.
- SMS — telefon raqami, kod va taymer.
- Qoida — "bu foydalanuvchi haqiqatan Aziz" degan xulosani faqat server chiqaradi. Frontend tugmalarni chizadi, yo'naltiradi, kodni yuboradi.
Hayotiy o'xshatish: mehmonxonaga pasport bilan kirish. Siz (ilova) pasport yasamaysiz — uni davlat (Google) beradi. Qabulxona (server) pasportning haqiqiyligini tekshiradi va xona kalitini (sessiyani) beradi. Eshik oldidagi qorovul (frontend) faqat "qabulxonaga marhamat" deydi.
vazifalar dagi kirish esa hali soxta — 18-qismdagi ism bilan "kirish". Haqiqiy autentifikatsiya backend bilan 28-qismda. Bu darsning kodi — UI va oqimlar, server tomoni soxta funksiyalar.
2. OAuth oqimi: kim nima qiladi
2.1 Authorization code
"Google bilan kirish" ortida OAuth 2.0 (ruxsat berish protokoli) va uning ustidagi OpenID Connect (kimlikni aytish qatlami) turadi. Nazariyasi — OAuth 2.0 va OpenID Connect darsida. Frontend uchun eng muhimi — oqim:
sequenceDiagram
participant B as Brauzer (React)
participant G as Google
participant S as Bizning server
B->>B: state + code_verifier yaratadi, saqlaydi
B->>G: /authorize?client_id&redirect_uri&state&code_challenge
G->>G: foydalanuvchi kiradi, ruxsat beradi
G-->>B: redirect_uri?code=…&state=…
B->>B: state mosmi?
B->>S: code + code_verifier
S->>G: code → token (client secret bilan)
G-->>S: id_token, access_token
S->>S: id_token'ni tekshiradi, foydalanuvchini topadi
S-->>B: sessiya cookieNimaga qarang: token brauzerga kelmaydi. Google code ni beradi — bir martalik, qisqa muddatli "kvitansiya". Uni tokenga faqat server almashtiradi (maxfiy client_secret bilan). Brauzer oxirida faqat o'z serveringizning sessiya cookie'sini oladi.
Yangi so'zlar:
redirect_uri(qaytish manzili) — Google kodni qaytaradigan sahifangiz, masalanhttps://example.com/auth/callback. Google Cloud Console'da aniq ro'yxatga yoziladi: boshqa manzilga Google kod bermaydi.code(avtorizatsiya kodi) — tokenga almashtiriladigan bir martalik qiymat.state— siz yaratgan tasodifiy satr. Google uni o'zgartirmasdan qaytaradi. Qaytganstatesaqlanganiga teng bo'lmasa — so'rovni siz boshlamagansiz.- PKCE (Proof Key for Code Exchange, "piksi" deb o'qiladi) —
codeni o'g'irlagan odam undan foydalana olmasligi uchun "kalit-qulf" juftligi.
2.2 PKCE: kalit va qulf
Brauzer tasodifiy code_verifier (kalit) yaratadi va uni o'zida saqlaydi. Google'ga esa faqat uning SHA-256 hash'ini — code_challenge (qulf) — yuboradi. Kodni tokenga almashtirishda server code_verifier ni ham beradi. Google hisoblaydi: SHA256(verifier) === challenge? Kod yo'lda o'g'irlansa ham, kalit o'g'rida yo'q.
function base64url(bytes) {
return btoa(String.fromCharCode(...bytes))
.replaceAll("+", "-")
.replaceAll("/", "_")
.replace(/=+$/, "");
}
function randomString() {
// 32 tasodifiy bayt → 43 belgi
return base64url(crypto.getRandomValues(new Uint8Array(32)));
}
async function challengeFor(verifier) {
const bytes = new TextEncoder().encode(verifier);
const digest = await crypto.subtle.digest("SHA-256", bytes);
return base64url(new Uint8Array(digest));
}
// Tekshirish uchun — doimiy kalit (haqiqiysi: randomString())
const verifier = "bahor-sinov-verifier-0123456789-abcdefghijk";
console.log(await challengeFor(verifier));
const state = randomString();
console.log(state.length, /^[A-Za-z0-9_-]+$/.test(state));Konsolda:
i3PYosuqXmk3c4UQjI9jH40PK4BjmGocYd3Rb9HegE8
43 truecrypto.getRandomValues— kriptografik tasodif.Math.random()bu yerda yaramaydi: uning keyingi qiymatini taxmin qilish mumkin.crypto.subtle.digest— brauzerdagi SHA-256 (Web Crypto). Asinxron —await. Natijani Node'dagicreateHash("sha256")bilan ham hisobladik — bir xil.- base64url — oddiy base64'ning manzilga xavfsiz varianti:
+→-,/→_,=yo'q. PKCE standarti aynan shuni talab qiladi.
Tekshirib ko'ring: Hujumchi
codeni ushlab oldi (masalan, brauzer tarixidan). Uning qo'lidacode_challengeham bor — u Google'ga yuborilgan manzilda edi. Nega baribir token ololmaydi?
Javob
Token uchun code_verifier kerak — challenge'dan verifier'ni topish uchun SHA-256 ni "orqaga aylantirish" kerak, bu amalda imkonsiz. Verifier faqat sizning brauzeringiz sessionStorage ida va serveringizga yuborilgan so'rovda bo'lgan. Bundan tashqari, server client_secret ni ham beradi — hujumchida u ham yo'q.
3. Frontend qismi: yo'naltirish va qaytish
3.1 «Google bilan kirish» tugmasi
Tugma bosilganda: state va verifier yaratiladi, sessionStorage ga yoziladi (tab yopilsa o'chadi — kerak ham emas) va brauzer Google'ga o'tadi. Manzilni frontend yoki server tuzishi mumkin — ko'p jamoalar serverni tanlaydi (/api/auth/google → 302 Google'ga). Bu yerda — frontend varianti, chunki PKCE'ning frontend roli shunda ko'rinadi:
const AUTHORIZE = "https://accounts.google.com/o/oauth2/v2/auth";
const CLIENT_ID = "SINOV.apps.googleusercontent.com"; // o'rinbosar
function base64url(bytes) {
return btoa(String.fromCharCode(...bytes))
.replaceAll("+", "-")
.replaceAll("/", "_")
.replace(/=+$/, "");
}
const randomString = () =>
base64url(crypto.getRandomValues(new Uint8Array(32)));
async function challengeFor(verifier) {
const bytes = new TextEncoder().encode(verifier);
const digest = await crypto.subtle.digest("SHA-256", bytes);
return base64url(new Uint8Array(digest));
}
async function startLogin() {
const state = randomString();
const verifier = randomString();
const saved = JSON.stringify({ state, verifier });
sessionStorage.setItem("oauth", saved);
const url = new URL(AUTHORIZE);
url.search = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: "https://example.com/auth/callback",
response_type: "code",
scope: "openid email profile",
state,
code_challenge: await challengeFor(verifier),
code_challenge_method: "S256",
});
// haqiqiy ilovada: location.assign(url)
for (const key of url.searchParams.keys()) console.log(key);
}
export default function App() {
return <button onClick={startLogin}>Google bilan kirish</button>;
}Konsolda:
client_id
redirect_uri
response_type
scope
state
code_challenge
code_challenge_methodManzil Google hujjatidagi parametrlar bilan tuzildi (developers.google.com/identity, "OAuth 2.0 for Web Server Applications"). CLIENT_ID — o'rinbosar: haqiqiy client id Google Cloud Console'da loyiha ochib olinadi, biz ochmadik. Konsolga qiymatlar emas, faqat nomlar chiqdi — state har safar boshqa.
scope: "openid email profile"— "kim ekanimni ayt": OpenID Connect'ning ID tokeni, email va ism. Ortiqcha ruxsat so'ramang — foydalanuvchi qo'rqadi.code_challenge_method: "S256"— SHA-256. Eskiplainusuli himoya bermaydi.
3.2 Qaytish sahifasi
Google mehmonni https://example.com/auth/callback?code=4/0Ab…&state=… ga qaytaradi. Rad etsa (mehmon «Bekor qilish» ni bossa) — ?error=access_denied&state=…. Qaytish sahifasining vazifalari:
stateni saqlangani bilan solishtirish. Mos emas — to'xtash.errorbo'lsa — tushunarli xabar va kirish sahifasiga qaytish.codeniverifierbilan serverga yuborish.sessionStorageni tozalash va manzildancodeni olib tashlash (replace) — kod tarixda qolmasin.- Kirishdan oldingi sahifaga qaytarish — faqat o'z yo'llaringizdan biri (ochiq yo'naltirish).
Natija oynasida manzil satri yo'q — shuning uchun xotiradagi router, boshlang'ich manzil initialEntries bilan (React Router). Server — soxta:
import { useEffect, useState } from "react";
import {
createMemoryRouter,
RouterProvider,
useNavigate,
useSearchParams,
} from "react-router";
// "Tugma bosilganda" saqlangan qiymatlar (o'quv modeli)
sessionStorage.setItem(
"oauth",
JSON.stringify({ state: "abc123", verifier: "kalit-xyz" }),
);
function exchangeCode(code, verifier) {
console.log("serverga:", { code, verifier });
return new Promise((resolve) =>
setTimeout(() => resolve({ name: "Aziz" }), 200),
);
}
function Callback() {
const [params] = useSearchParams();
const navigate = useNavigate();
const [message, setMessage] = useState("Kirilmoqda…");
useEffect(() => {
const raw = sessionStorage.getItem("oauth") ?? "null";
const saved = JSON.parse(raw);
sessionStorage.removeItem("oauth"); // bir martalik
if (!saved || params.get("state") !== saved.state) {
setMessage("Xavfsizlik tekshiruvi o'tmadi. Qaytadan kiring.");
return;
}
if (params.get("error")) {
setMessage("Kirish bekor qilindi.");
return;
}
exchangeCode(params.get("code"), saved.verifier).then((user) => {
console.log("sessiya ochildi:", user.name);
navigate("/", { replace: true }); // code tarixda qolmasin
});
}, [params, navigate]);
return <p role="status">{message}</p>;
}
const router = createMemoryRouter(
[
{ path: "/", element: <p>Bosh sahifa: xush kelibsiz!</p> },
{ path: "/auth/callback", element: <Callback /> },
],
{ initialEntries: ["/auth/callback?code=4-0Ab&state=abc123"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
serverga: {code: '4-0Ab', verifier: 'kalit-xyz'}
sessiya ochildi: AzizEkranda:
Bosh sahifa: xush kelibsiz!(Natija oynasida manzil satri yo'q, shuning uchun createMemoryRouter. Loyihada — createBrowserRouter.)
initialEntries dagi state ni abc999 ga o'zgartirib ko'ring — ekranda «Xavfsizlik tekshiruvi o'tmadi», serverga hech narsa ketmaydi. Bu — CSRFga qarshi himoya (CSRF va SameSite): hujumchi o'z code i bilan sizning qaytish sahifangizga havola yuborsa, sizning brauzeringizda unga mos state yo'q. Aks holda siz hujumchining akkauntiga kirib qolardingiz va kiritgan ma'lumotingiz unga ketardi.
Diqqat: React StrictMode dev'da effektni ikki marta ishlatadi (StrictMode). Bizning effekt birinchi marta
sessionStorageni tozalaydi — ikkinchi martasavednull, xabar "tekshiruv o'tmadi" bo'lib qolardi. Haqiqiy loyihada qaytish sahifasida "bir marta ishladi" belgisiniuseRefda saqlang yoki kod almashishni routerloaderiga ko'chiring (loader va action) — loader StrictMode'da ikki marta chaqirilmaydi.
4. Google Identity Services
4.1 Tayyor tugma va ID token
Google yana bir yo'l beradi — Google Identity Services (GIS): tayyor «Sign in with Google» tugmasi va "One Tap" oynasi (sahifa burchagida "Aziz sifatida davom etish"). U boshqa oqim bilan ishlaydi: Google oynasi (popup) ochiladi va JS callback'ingizga to'g'ridan ID token (credential — JWT) keladi. Qaytish sahifasi kerak emas.
// index.html'da:
// <script src="https://accounts.google.com/gsi/client" async>
useEffect(() => {
google.accounts.id.initialize({
client_id: import.meta.env.VITE_GOOGLE_CLIENT_ID,
callback: ({ credential }) => {
// credential — JWT (ID token). Uni o'qib "kirdi" demang!
void fetch("/api/auth/google", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ credential }),
});
},
});
google.accounts.id.renderButton(buttonRef.current!, {
theme: "outline",
size: "large",
text: "signin_with",
});
}, []);(Hujjat bo'yicha — developers.google.com/identity/gsi/web. Client id va Google loyihasi yo'q — ishga tushirilmagan.)
credential ni brauzerda ochib (atob(token.split(".")[1])) ism va emailni ko'rish mumkin. Lekin "token ichida Aziz yozilgan — demak Aziz" deyish xato: hujumchi istalgan JSON'li "token" yasaydi. Server tokenning imzosini Google ochiq kalitlari bilan, aud (sizning client id), iss (Google), exp (muddat) va email_verified ni tekshiradi (JWT anatomiyasi, Google bilan kirish).
React uchun o'ram ham bor: @react-oauth/google (<GoogleLogin onSuccess={…} />, useGoogleLogin). Ichida — o'sha GIS skripti.
4.2 CSP va maxfiylik narxi
GIS — Google serveridan yuklanadigan skript va iframe. Google hujjatidagi CSP ro'yxati:
script-src https://accounts.google.com/gsi/client
frame-src https://accounts.google.com/gsi/
connect-src https://accounts.google.com/gsi/
style-src https://accounts.google.com/gsi/stylevazifalar kabi Trusted Types yoqilgan sahifada tashqi skript yuklovchi vidjetlar (CAPTCHA'lar) TrustedScriptURL buzilishi bilan to'xtadi — keyingi darsda o'lchov. GIS ni kanon sinamadi (kalit yo'q), lekin xavf bir xil: <script> ni HTML'da ulash (JS'dan emas) yoki TT siyosatiga accounts.google.com ni qo'shish kerak bo'ladi. Maxfiylik: One Tap sahifa ochilishi bilan Google'ga so'rov yuboradi — mehmon hali hech narsa bosmagan bo'lsa ham. Bu cookie va rozilik savoli (analitika dagi kabi).
Redirect («OAuth oqimi» dagi) va GIS orasida tanlov: redirect — CSP'ga bitta yo'naltirish, sahifaga begona skript yo'q, har qanday OAuth provayder bilan bir xil. GIS — chiroyliroq, One Tap bilan konversiya yuqori, lekin Google skripti sahifangizda.
5. SMS kod bilan kirish
5.1 Ikki ekran
O'zbekistonda eng ko'p uchraydigan kirish — telefon raqami va SMS kod (OTP — one-time password). Oqim:
stateDiagram-v2
[*] --> Raqam
Raqam --> Yuborilmoqda: «Kod yuborish»
Yuborilmoqda --> Kod: server: SMS ketdi
Yuborilmoqda --> Raqam: xato (noto'g'ri raqam, 429)
Kod --> Tekshirilmoqda: 6 raqam
Tekshirilmoqda --> Kirdi: to'g'ri
Tekshirilmoqda --> Kod: noto'g'ri (urinish qoldi)
Kod --> Yuborilmoqda: «Qayta yuborish» (taymer tugagach)
Kod --> Raqam: «Raqamni o'zgartirish»
Kirdi --> [*]Nimaga qarang: har strelka — foydalanuvchi ko'radigan holat. "Yuborilmoqda" da tugma o'chiq, "noto'g'ri kod" da xabar va qolgan urinishlar, "qayta yuborish" — faqat taymer tugagach. Bu — holat mashinasi fikrlashi.
Telefon maskasi (+998 (__) ___-__-__) va OTP maydoni (autoComplete="one-time-code", inputMode="numeric", bitta maydon yoki input-otp) — Maxsus inputlar darsida yozilgan. Bu darsda — ular atrofidagi oqim.
5.2 Qayta yuborish taymeri
SMS kechikishi mumkin. Mehmon 5 soniyadan keyin «Qayta yuborish» ni besh marta bossa — besh SMS, besh baravar xarajat, va eski kodlar bekor bo'ladi. Shuning uchun tugma odatda 60 soniya o'chiq va qolgan vaqtni ko'rsatadi.
Natija oynasida bir daqiqa kutish noqulay — o'quv modelida "1 soniya" = 100 ms va taymer 3 "soniya":
import { useEffect, useState } from "react";
const SECOND = 100; // o'quv modeli; haqiqiysi — 1000
const RESEND_AFTER = 3; // haqiqiysi — 60
function sendCode(phone) {
console.log("server: SMS →", phone);
return new Promise((resolve) => setTimeout(resolve, 50));
}
function useCountdown(seconds, runId) {
const [left, setLeft] = useState(seconds);
useEffect(() => {
setLeft(seconds);
const id = setInterval(() => {
setLeft((s) => {
if (s <= 1) clearInterval(id);
return Math.max(s - 1, 0);
});
}, SECOND);
return () => clearInterval(id);
}, [seconds, runId]);
return left;
}
function CodeStep({ phone, sentCount, onResend }) {
const left = useCountdown(RESEND_AFTER, sentCount);
return (
<section>
<p>{phone} raqamiga kod yuborildi.</p>
<label htmlFor="otp">SMS kod</label>
<input
id="otp"
inputMode="numeric"
autoComplete="one-time-code"
/>
<p>
<button disabled={left > 0} onClick={onResend}>
{left > 0
? `Qayta yuborish (${left} s)`
: "Kodni qayta yuborish"}
</button>
</p>
</section>
);
}
export default function App() {
const [phone, setPhone] = useState("");
const [sentCount, setSentCount] = useState(0);
async function send() {
await sendCode(phone);
setSentCount((n) => n + 1);
}
if (sentCount === 0) {
return (
<section>
<label htmlFor="tel">Telefon</label>
<input
id="tel"
type="tel"
autoComplete="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
<button onClick={send}>Kod yuborish</button>
</section>
);
}
return (
<CodeStep phone={phone} sentCount={sentCount} onResend={send} />
);
}Konsolda:
server: SMS → +998 90 000 00 00Ekranda:
+998 90 000 00 00 raqamiga kod yuborildi.
SMS kod
Kodni qayta yuborish- Oraliq nuqta (
ekran("Qayta yuborish (")) — kod yuborilgach tugma o'chiq va soniyalarni sanaydi. Oxirida — faol «Kodni qayta yuborish». useCountdown(seconds, runId)—sentCounto'zgarsa (qayta yuborildi), effekt qayta boshlanadi va taymer yana 3 dan sanaydi.clearIntervalcleanup'da — ekran almashsa (masalan, «Raqamni o'zgartirish»), taymer to'xtaydi.
Haqiqiy ilovada ikki narsa qo'shiladi: taymer server javobidan ({ resendAfter: 60 }) — server o'zi ham cheklaydi; va sahifa yangilansa taymer qaytadan boshlanmasligi uchun "qachongacha" vaqtni sessionStorage ga yozish (3-mashq).
5.3 Server nima qiladi (va frontend nimani ko'rsatadi)
SMS'ni frontend yubormaydi — provayder kaliti (Eskiz, PlayMobile) brauzerda bo'lsa, har kim uning hisobidan SMS yuboradi. Server qiladi (OTP orqali login, SMS provayderlar):
- kodni yaratadi (kriptografik tasodif), hash qilib saqlaydi, muddat 2–5 daqiqa;
- urinishlarni sanaydi (masalan, 5 ta), keyin kod bekor;
- bir raqamga va bir IP'dan yuborishni cheklaydi.
Frontend bu cheklovlarning javoblarini chiroyli ko'rsatishi kerak:
| Server javobi | Ekranda |
|---|---|
400 noto'g'ri raqam |
«Raqamni tekshiring: +998 XX XXX XX XX» |
401 noto'g'ri kod, qoldi 3 |
«Kod noto'g'ri. Yana 3 urinish» |
410 kod eskirgan |
«Kod muddati tugadi. Yangisini oling» + qayta yuborish |
429 juda ko'p |
«Juda ko'p urinish. 10 daqiqadan keyin qayta urinib ko'ring» |
5.4 Biz sinab ko'rdik: input-otp
Kanon tajribasida input-otp 1.5.0 (tajriba/kutubxonalar/src/k/otp.tsx) o'lchandi:
otp JS 71475 (+ 3.7 KB) CSS 0 | CSP style-src-elem×1 | xato: Applying inline style violates the following Content Security Policy directive 'style-src 'self''. E | ochiq: tashqi yo'qKichik (+3.7 KB) va tashqi serverga bormaydi. Lekin u sahifaga o'z <style> tegini qo'shadi — vazifalar siyosati (style-src 'self') uni to'sadi. Ekranda katakchalar chiqdi (______), lekin kutubxona yashirin <input> uchun qo'shmoqchi bo'lgan uslublar qo'llanmadi. Qat'iy CSP'li loyihada — oddiy bitta maydon (19/#35 dagi) yoki CSP nonce.
5.5 Nega vazifalar da yo'q
Haqiqiy kirish — backend: foydalanuvchilar jadvali, sessiyalar, SMS provayder hisobi, Google loyihasi. Kanonda auth — 18/#42 dagi soxta kirish va 19/#07 dagi himoyalangan marshrutlar. Ular 28-qismda haqiqiy backend'ga ulanadi: o'shanda bu darsdagi qaytish sahifasi va SMS ekranlari kerak bo'ladi.
6. Tayyor xizmatlar: Clerk, Auth0 va boshqalar
Auth'ni noldan yozish — ko'p ish va ko'p xavf. Tayyor xizmatlar hammasini beradi:
| Xizmat | Frontend'da nima beradi |
|---|---|
| Clerk | tayyor React komponentlar (<SignIn />, <UserButton />), Google/SMS/email bir sozlamada |
| Auth0 (Okta) | hosted login sahifasi (redirect), @auth0/auth0-react, korporativ SSO |
| Firebase Auth, Supabase Auth | SDK + o'z ma'lumotlar bazasi bilan birga |
| Better Auth, Auth.js | ochiq kod, o'z serveringizda (Next.js/Node) |
Narxi: bepul chegara (oylik foydalanuvchilar soni) dan keyin — pullik; foydalanuvchilar ma'lumoti — xizmat serverida (shaxsga doir ma'lumot qonuni!); SMS O'zbekiston raqamlariga qimmat yoki cheklangan bo'lishi mumkin. Davlat identifikatsiyasi (OneID) esa faqat o'z integratsiyangiz bilan (OneID). Qachon qaysi biri — Auth kutubxonalari va xizmatlari darsida.
7. Hujumchi nigohi
1. Login CSRF. Hujumchi o'z Google akkauntiga kirib, code ni oladi va sizga havola yuboradi: example.com/auth/callback?code=<uning-kodi>. state tekshirilmasa, siz uning akkauntiga kirasiz va saqlagan manzilingiz, kartangiz unga ketadi. Himoya — state («Qaytish sahifasi» dagi tekshiruv).
2. Ochiq yo'naltirish. ?keyin=https://yomon.example — kirishdan keyin hujumchi sahifasiga. U yerda "Sessiya tugadi, parolni qayta kiriting" soxta formasi. Himoya: keyin faqat / bilan boshlanadigan o'z yo'lingiz (19/#07).
3. Token brauzerda. ID token yoki access token'ni localStorage ga yozish — har qanday XSS uni o'g'irlaydi. Himoya: token serverda qoladi, brauzerda — HttpOnly sessiya cookie (token qayerda saqlanadi).
4. OTP brute force. 6 xonali kod — million variant. Cheksiz urinish bo'lsa, skript bir necha soatda topadi. Himoya — server: urinishlar chegarasi, kod muddati, bir raqamga so'rovlar chegarasi. Frontend'dagi "3 urinish qoldi" — faqat ko'rsatish.
5. SMS pumping. Hujumchi skript bilan sizning «Kod yuborish» tugmangizni minglab pullik (ko'pincha xorijiy premium) raqamlarga bosadi. Har SMS — sizning hisobingizdan. Bir kechada katta hisob. Himoya: faqat kerakli mamlakat kodlari (+998), IP va raqam bo'yicha chegara, CAPTCHA — keyingi darsning mavzusi.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
redirect_uri_mismatch (Google xato sahifasi) |
manzil Console'dagi ro'yxatda aynan yo'q (/ oxirida, http / https) |
ro'yxatga aynan shu manzil |
| Qaytish sahifasi dev'da "tekshiruv o'tmadi" | StrictMode effektni ikki marta ishlatdi, state birinchida o'chirildi |
useRef belgisi yoki loader |
invalid_grant |
code ikki marta ishlatildi yoki eskirdi |
kod bir martalik — qayta urinishda yangi login |
PKCE invalid_request |
code_challenge oddiy base64 (+, /, =) |
base64url |
| Taymer sahifa yangilanganda 60 dan boshlanadi | taymer faqat state'da | "qachongacha" vaqtni sessionStorage ga |
| Bir bosishda ikki SMS | tugma so'rov paytida faol | disabled + server chegarasi |
| GIS tugmasi chiqmadi, konsolda CSP | accounts.google.com CSP'da yo'q |
Google ro'yxatidagi direktivalar |
9. Mashqlar
Mashqlar kurs/mashqlar/21/24-tashqi-login/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): bo'sh joylarni to'ldiring
- CSRF'dan himoya uchun OAuth so'roviga qo'shib, qaytganda solishtiriladigan qiymat:
- PKCE'da Google'ga yuboriladigan qism (hash):
- Kodni tokenga almashtiradigan tomon (brauzer/server):
- SMS kod maydonini avto-to'ldirish uchun
autoCompleteqiymati: - Ko'p SMS yuborib, sizni pulga tushiradigan hujum: SMS
Yechim
state — tasodifiy, sessionStorage da, qaytganda teng bo'lishi shart. code_challenge = base64url(SHA256(code_verifier)). Token — serverda (client_secret bilan), brauzerga faqat sessiya cookie. one-time-code — telefon SMS'dagi kodni taklif qiladi. SMS pumping — CAPTCHA va chegaralar bilan to'siladi.
2-mashq (o'rta): qaytish sahifasining qarori
Qaytish sahifasining mantiqini sof funksiya qiling: decide(search, saved) — search — ?code=…&state=… satri, saved — { state, verifier } yoki null. Natija:
{ type: "exchange", code, verifier }— hammasi joyida;{ type: "cancelled" }—error=access_denied;{ type: "invalid" }—savedyo'q,statemos emas yokicodeyo'q.
state tekshiruvi error dan oldin bo'lsin. Besh holat bilan sinang.
Ishora: new URLSearchParams(search); "avval xavfsizlik, keyin qolgani" (erta qaytish).
Yechim
function decide(search, saved) {
const params = new URLSearchParams(search);
if (!saved || params.get("state") !== saved.state) {
return { type: "invalid" };
}
if (params.get("error")) return { type: "cancelled" };
const code = params.get("code");
if (!code) return { type: "invalid" };
return { type: "exchange", code, verifier: saved.verifier };
}
const saved = { state: "abc", verifier: "k1" };
console.log(decide("?code=4-0A&state=abc", saved));
console.log(decide("?error=access_denied&state=abc", saved));
console.log(decide("?error=access_denied&state=xyz", saved));
console.log(decide("?code=4-0A&state=abc", null));
console.log(decide("?state=abc", saved));Konsolda:
{ type: 'exchange', code: '4-0A', verifier: 'k1' }
{ type: 'cancelled' }
{ type: 'invalid' }
{ type: 'invalid' }
{ type: 'invalid' }Uchinchi holat muhim: error bor, lekin state begona — "bekor qilindi" emas, "invalid". Hujumchi sizni "bekor qilindi" sahifasiga yo'naltira olmasin. Sof funksiya — node:test bilan oson tekshiriladi; komponent esa faqat natijaga qarab chizadi.
3-mashq (qiyin): yangilanishga chidamli taymer
«Qayta yuborish taymeri» dagi useCountdown ni o'zgartiring: u "necha soniya qoldi" emas, "qachongacha" vaqtni (until, millisoniya) qabul qilsin va qolgan soniyani Math.ceil((until - Date.now()) / SECOND) bilan hisoblasin. until — sessionStorage dagi "sms:until" dan (bo'lmasa — 0).
Sinov: blok ochilganda sessionStorage ga "1.5 soniyadan keyin" yozilgan bo'lsin (sahifa yangilanganini taqlid qiladi). Ekranda avval «Qayta yuborish (2 s)» (bir lahzadan keyin «(1 s)»), keyin — «Kodni qayta yuborish».
Yechim
import { useEffect, useState } from "react";
const SECOND = 100; // o'quv modeli
// "Sahifa yangilandi": oldingi yuborishdan 1.5 "soniya" qolgan
const until = Date.now() + 1.5 * SECOND;
sessionStorage.setItem("sms:until", String(until));
function secondsLeft(until) {
return Math.max(0, Math.ceil((until - Date.now()) / SECOND));
}
function useCountdown(until) {
const [left, setLeft] = useState(() => secondsLeft(until));
useEffect(() => {
setLeft(secondsLeft(until));
const id = setInterval(() => {
const s = secondsLeft(until);
setLeft(s);
if (s === 0) clearInterval(id);
}, SECOND / 4);
return () => clearInterval(id);
}, [until]);
return left;
}
export default function App() {
const [until] = useState(() =>
Number(sessionStorage.getItem("sms:until") ?? 0),
);
const left = useCountdown(until);
return (
<button disabled={left > 0}>
{left > 0
? `Qayta yuborish (${left} s)`
: "Kodni qayta yuborish"}
</button>
);
}Ekranda:
Kodni qayta yuborishTaymer endi sanoqqa emas, soatga qaraydi: sahifa yangilansa ham, tab fonda "uxlab" qolsa ham (brauzer fondagi setInterval ni sekinlashtiradi — taymerlar) qolgan vaqt to'g'ri. Interval chorak soniyada tekshiradi — ko'rsatkich o'z vaqtida o'zgaradi. «Kod yuborish» bosilganda sessionStorage.setItem("sms:until", Date.now() + 60_000) yoziladi.
4-mashq: tajriba qadami (ixtiyoriy)
vazifalar ga tegmang. Alohida papkada (kurs/tajriba/oauth/) qaytish sahifasini React Router loader i bilan qayta yozing (loader va action): loader state ni tekshiradi, soxta exchangeCode ni chaqiradi va redirect("/") qaytaradi. StrictMode'da konsolda serverga: necha marta chiqqanini darsdagi effekt varianti bilan solishtiring.
Yechim
Loader varianti:
async function callbackLoader({ request }) {
const saved = JSON.parse(sessionStorage.getItem("oauth") ?? "null");
sessionStorage.removeItem("oauth");
const decision = decide(new URL(request.url).search, saved);
if (decision.type !== "exchange") return redirect("/kirish?xato=1");
await exchangeCode(decision.code, decision.verifier);
return redirect("/");
}Loader navigatsiya paytida bir marta chaqiriladi — StrictMode uni ikki marta ishlatmaydi. Effekt variantida esa dev'da birinchi chaqiruv sessionStorage ni tozalaydi va ikkinchisi "invalid" ga tushadi (darsdagi «Diqqat»). decide — 2-mashqdan.
10. Real ishda
- Deyarli har O'zbekiston xizmati (taksi, yetkazib berish, bank ilovalari) SMS kod bilan kiradi; xalqaro va dasturchilar uchun xizmatlar — Google, GitHub, Apple bilan. Frontend'chi ikkalasining ekranlarini, xato matnlarini va taymerini yozadi.
- BFF (backend for frontend) — ko'p jamoalarda OAuth'ni butunlay server bajaradi: tugma
/api/auth/googlega havola, qaytish ham serverga, brauzer faqat cookie oladi. Frontend'da PKCE kodi umuman yo'q — bu ham to'g'ri va xavfsiz yo'l. - Intervyu savollari: "OAuth'da
statenima uchun?", "PKCE qanday ishlaydi?", "Nega token'ni brauzerda saqlamaslik kerak?", "SMS OTP'ni qanday himoyalaysiz?", "Qayta yuborish taymerini qanday yozasiz?".
Xulosa
- Tashqi login: kimlikni Google yoki operator tasdiqlaydi, xulosani — faqat server chiqaradi; frontend — tugma, yo'naltirish, qaytish sahifasi va xabarlar.
- OAuth authorization code:
state(CSRF), PKCE (code_verifierbrauzerda,code_challenge = base64url(SHA256)Google'ga),code→ server → token; brauzerga — sessiya cookie. - Qaytish sahifasi: avval
state, keyinerror, keyincode;replacebilan tarixdan olib tashlash; StrictMode'da bir marta ishlashiga e'tibor. - GIS — tayyor tugma va One Tap,
credential(ID token) faqat serverda tekshiriladi; CSP'gaaccounts.google.com/gsi/…. - SMS kod: raqam → kod ekrani, qayta yuborish taymeri (soatga qarab), server javoblari (noto'g'ri, eskirgan, 429) uchun aniq matn; kod va chegaralar — serverda;
input-otp+3.7 KB, o'z<style>i — CSP.
Keyingi dars: CAPTCHA: Turnstile va reCAPTCHA — SMS pumping va spam botlarga qarshi. Vidjet token beradi, server uni tekshiradi. Provayderlarning test kalitlari bilan o'lchangan CSP natijasi va honeypot maydon.
Manbalar
- Google Identity: "OAuth 2.0 for Web Server Applications", "Sign in with Google for Web" (GIS,
initialize,renderButton, CSP), "Verify the Google ID token" — developers.google.com/identity - RFC 7636 "Proof Key for Code Exchange" va "OAuth 2.0 for Browser-Based Applications" (IETF) — datatracker.ietf.org
- MDN:
Crypto.getRandomValues(),SubtleCrypto.digest(),autocomplete="one-time-code"— developer.mozilla.org - React Router:
createMemoryRouter,useSearchParams,loader,redirect— reactrouter.com - input-otp — github.com/guilhermerodz/input-otp
- Kanon:
tajriba/kutubxonalar/src/k/otp.tsx,natijalar/tajriba/kutubxonalar-21.txt; soxta auth —vazifalarsrc/xususiyatlar/auth/(2026-10-17)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!