Kitobchi API v1
OpenAPI Postman Kitobchi.com

Integratsiya

Bosqichma-bosqich: authorize, callback, token almashtirish, ID tokenni tekshirish, userinfo, refresh, chiqish va kod namunalari.

1. Foydalanuvchini yo'naltirish

Har bir kirish urinishi uchun serveringizda uchta tasodifiy qiymat yarating va foydalanuvchi sessiyasida (yoki qisqa muddatli httpOnly cookie'da) saqlang:

QiymatQanday yaratiladiNima uchun
state≥ 32 bayt tasodifiy, base64urlCSRF va javobni so'rovga bog'lash
nonce≥ 32 bayt tasodifiyID token qayta ishlatilishining oldini olish
code_verifier32 bayt tasodifiy → base64url (43 belgi)PKCE — kod o'g'irlansa ham almashtirib bo'lmaydi

code_challenge = BASE64URL(SHA256(code_verifier)). Faqat S256 qabul qilinadi; plain rad etiladi.

HTTP — brauzerni yo'naltirish
GET https://kitobchi.com/api/id/authorize
  ?response_type=code
  &client_id=kid_xxxxxxxxxxxxxxxxxxxxxxxx
  &redirect_uri=https%3A%2F%2Fhamkor.uz%2Fauth%2Fkitobchi%2Fcallback
  &scope=openid%20profile%20phone
  &state=Q2xpZW50U3RhdGVSYW5kb20xMjM0NTY3ODkw
  &nonce=Tm9uY2VSYW5kb21WYWx1ZTEyMzQ1Njc4OTA
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
ParametrTavsifTalab
response_typeFaqat codemajburiy
client_idSizga berilgan identifikatormajburiy
redirect_uriRo'yxatdagilardan biri, belgi-belgi bir xilmajburiy
scopeBo'sh joy bilan ajratilgan, openid bo'lishi shartmajburiy
state1–512 belgimajburiy
nonce1–255 belgi, ID token ichida qaytadimajburiy
code_challenge, code_challenge_methodPKCE, S256majburiy
promptnone (jim tekshiruv), login (qayta kirish), consent (rozilikni qayta so'rash), select_accountixtiyoriy
max_ageSoniyalarda; oxirgi kirish bundan eski bo'lsa, qayta SMS so'raladiixtiyoriy
login_hintTelefon raqam (998901234567) — maydon oldindan to'ldiriladiixtiyoriy

2. Callback'ni qabul qilish

Muvaffaqiyat
https://hamkor.uz/auth/kitobchi/callback?code=SplxlOBeZQQYbYS6WxSbIA...&state=Q2xpZW50...&iss=https%3A%2F%2Fkitobchi.com%2Fapi%2Fid
Xato yoki rad etish
https://hamkor.uz/auth/kitobchi/callback?error=access_denied&error_description=...&state=Q2xpZW50...&iss=...

3. Kodni tokenlarga almashtirish

So'rov application/x-www-form-urlencoded bo'lishi shart (JSON qabul qilinmaydi). Server ilovalar client_secret_basic (tavsiya) yoki client_secret_post; public ilovalar faqat client_id + PKCE.

bash
curl -X POST 'https://kitobchi.com/api/id/token' \
  -u 'kid_xxxxxxxxxxxxxxxxxxxxxxxx:kid_cs_xxxxxxxx' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'code=SplxlOBeZQQYbYS6WxSbIA...' \
  --data-urlencode 'redirect_uri=https://hamkor.uz/auth/kitobchi/callback' \
  --data-urlencode 'code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'
JSON — 200 OK (Cache-Control: no-store)
{
  "access_token": "eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ii4uLiJ9...",
  "token_type": "Bearer",
  "expires_in": 600,
  "scope": "openid profile phone offline_access",
  "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ii4uLiJ9...",
  "refresh_token": "kid_rt_Wm9yQmlyb3JSYW5kb21Ub2tlbi4uLg"
}

refresh_token faqat offline_access so'ralganda (va ilovaga ruxsat berilganda) qaytadi.


4. ID tokenni tekshirish

ID token — RS256 bilan imzolangan JWT. Uni tayyor kutubxona bilan tekshiring; quyidagilarning barchasi bajarilishi shart:

  1. Imzo — JWKS'dagi kid mos kalit bilan; alg faqat RS256 (none/HS256 ni rad eting).
  2. iss = https://kitobchi.com/api/id
  3. aud = sizning client_id, azp ham shunga teng.
  4. exp kelajakda (soat farqi uchun ≤ 60 s leeway), iat juda eski emas.
  5. nonce sessiyadagi qiymatga teng.
  6. Kerak bo'lsa auth_time ni max_age bilan solishtiring.
ID token payload — misol
{
  "iss": "https://kitobchi.com/api/id",
  "sub": "f3Qx0rH9K1c4b7mZpLw2aVd8Yt6NsE5uRj0oGiBkCqA",
  "aud": "kid_xxxxxxxxxxxxxxxxxxxxxxxx",
  "azp": "kid_xxxxxxxxxxxxxxxxxxxxxxxx",
  "iat": 1792526400,
  "exp": 1792530000,
  "auth_time": 1792526380,
  "nonce": "Tm9uY2VSYW5kb21WYWx1ZTEyMzQ1Njc4OTA",
  "at_hash": "77QmUPtjPfzWtF2AnpK9RQ",
  "amr": ["otp"],
  "name": "Aziz Karimov",
  "given_name": "Aziz",
  "family_name": "Karimov",
  "picture": "https://kitobchi.com/storage/avatar/....jpg",
  "phone_number": "+998901234567",
  "phone_number_verified": true
}
Access tokenni o'qimang. Access token ham JWT, lekin u siz uchun «shaffof emas» deb hisoblang — undagi claim'lar ogohlantirishsiz o'zgarishi mumkin. Foydalanuvchi ma'lumotini ID tokendan yoki /userinfo dan oling.

5. UserInfo

bash
curl 'https://kitobchi.com/api/id/userinfo' -H 'Authorization: Bearer eyJ0eXAiOiJhdCtqd3Qi...'
JSON — 200 OK
{
  "sub": "f3Qx0rH9K1c4b7mZpLw2aVd8Yt6NsE5uRj0oGiBkCqA",
  "name": "Aziz Karimov",
  "phone_number": "+998901234567",
  "phone_number_verified": true
}

Javobdagi sub ID tokendagi bilan bir xil bo'lishi shart — farq qilsa, javobni rad eting. Token yaroqsiz bo'lsa 401 va WWW-Authenticate: Bearer error="invalid_token".


6. Refresh token (aylanuvchi)

bash
curl -X POST 'https://kitobchi.com/api/id/token' \
  -u 'kid_xxxxxxxxxxxxxxxxxxxxxxxx:kid_cs_xxxxxxxx' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode 'refresh_token=kid_rt_Wm9yQmlyb3JSYW5kb21Ub2tlbi4uLg'

7. Chiqish va bekor qilish

Foydalanuvchi ilovangizdan chiqsa, refresh tokenni bekor qiling (javob doim 200):

bash
curl -X POST 'https://kitobchi.com/api/id/revoke' \
  -u 'kid_xxxxxxxxxxxxxxxxxxxxxxxx:kid_cs_xxxxxxxx' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'token=kid_rt_Wm9yQmlyb3JSYW5kb21Ub2tlbi4uLg' \
  --data-urlencode 'token_type_hint=refresh_token'

Kitobchi ID sessiyasini ham yopish kerak bo'lsa (umumiy kompyuterlar), brauzerni yo'naltiring. post_logout_redirect_uri faqat ro'yxatdan o'tgan va faqat imzosi to'g'ri id_token_hint bilan ishlaydi:

HTTP
GET https://kitobchi.com/api/id/logout?id_token_hint=eyJ...&post_logout_redirect_uri=https%3A%2F%2Fhamkor.uz%2F&state=xyz

Ekotizim ilovalari: brauzersiz kirish

Kitobchi ekotizimiga kiruvchi (first-party) mobil ilovalar foydalanuvchini brauzerga chiqarmasdan, o'z ekranida kirita oladi: ilova raqam va kodni o'zi so'raydi, autentifikatsiyani esa Kitobchi ID bajaradi. Bu imkoniyat faqat Kitobchi tomonidan first_party deb belgilangan ilovalarga ochiq; hamkor (uchinchi tomon) ilovalar yuqoridagi brauzer oqimidan foydalanadi — foydalanuvchi Kitobchi sahifasi va rozilik oynasini ko'rishi shart.

1

Kod so'rash

Ilova PKCE juftligi va nonce yaratib, raqamni yuboradi. Raqam Kitobchi'da mavjud va foydalanuvchining Kitobchi ilovasi faol bo'lsa, kod Kitobchi ilovasidagi rasmiy «Kitobchi» chatiga (push bilan) yuboriladi — SMS ketmaydi. Aks holda SMS yuboriladi.

2

SMS zaxirasi

channel = app bo'lsa, ekranda «Kod Kitobchi ilovasiga yuborildi» deb yozing va sms_available_in soniyadan keyin «SMS orqali olish» tugmasini faollashtiring — u o'sha kodni SMS bilan yuboradi.

3

Kodni tasdiqlash va tokenlar

Kod to'g'ri bo'lsa authorization_code qaytadi — uni odatdagidek /token ga code_verifier bilan almashtirasiz.

1. POST /native/start
curl -X POST 'https://kitobchi.com/api/id/native/start' -H 'Content-Type: application/json' -d '{
  "client_id": "kid_xxxxxxxxxxxxxxxxxxxxxxxx",
  "phone": "+998901234567",
  "scope": "openid profile phone offline_access",
  "redirect_uri": "uz.kitobchi.express:/oauth",
  "code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
  "code_challenge_method": "S256",
  "nonce": "Tm9uY2VSYW5kb21WYWx1ZQ"
}'

// 200
{ "auth_session": "q8Xw...", "masked_phone": "+998 90 *** ** 67",
  "expires_in": 300, "channel": "app", "sms_available_in": 30 }
2. POST /native/resend — SMS orqali
{ "client_id": "kid_…", "auth_session": "q8Xw..." }
// 200 → { "channel": "sms" }   |   30 s o'tmagan bo'lsa → { "channel": "app", "retry_after": 12 }
3. POST /native/verify
{ "client_id": "kid_…", "auth_session": "q8Xw...", "code": "482913" }
// 200 → { "authorization_code": "SplxlOBe...", "redirect_uri": "uz.kitobchi.express:/oauth", "iss": "https://kitobchi.com/api/id" }
// keyin: POST /token  grant_type=authorization_code, code, redirect_uri, code_verifier, client_id
errorHTTPMa'nosi
unauthorized_client403Ilova first-party emas — brauzer oqimidan foydalaning
invalid_phone400Raqam formati noto'g'ri
invalid_code400Kod noto'g'ri (5 urinishgacha)
too_many_attempts, expired_code, invalid_session429 / 400Jarayonni qaytadan boshlang (/native/start)
daily_limit429Bu raqamga bugun juda ko'p kod yuborilgan
delivery_failed503Kod yuborilmadi, birozdan keyin urinib ko'ring
Kod qayerda ko'rinadi: Kitobchi ilovasidagi «Kitobchi» chatida maxsus kartochka (nusxalash tugmasi, amal qilish vaqti) va bildirishnomada. Kodning o'zi chat xabarlari bazasiga yozilmaydi va Kitobchi xodimlari uni ko'ra olmaydi; kod ishlatilgach yoki 5 daqiqadan keyin kartochkada ko'rsatilmaydi.

Kod namunalari

Node.js (openid-client v6)

JavaScript
import * as client from 'openid-client'

const config = await client.discovery(
  new URL('https://kitobchi.com/api/id'),
  process.env.KITOBCHI_CLIENT_ID,
  process.env.KITOBCHI_CLIENT_SECRET,
)

// /login
const code_verifier = client.randomPKCECodeVerifier()
const code_challenge = await client.calculatePKCECodeChallenge(code_verifier)
const state = client.randomState()
const nonce = client.randomNonce()
req.session.kid = { code_verifier, state, nonce }
res.redirect(client.buildAuthorizationUrl(config, {
  redirect_uri: 'https://hamkor.uz/auth/kitobchi/callback',
  scope: 'openid profile phone',
  code_challenge, code_challenge_method: 'S256', state, nonce,
}).href)

// /auth/kitobchi/callback
const { code_verifier, state, nonce } = req.session.kid
delete req.session.kid
const tokens = await client.authorizationCodeGrant(config, currentUrl, {
  pkceCodeVerifier: code_verifier, expectedState: state, expectedNonce: nonce,
})                      // iss, state, imzo, aud, exp, nonce — kutubxona tekshiradi
const claims = tokens.claims()   // claims.sub — foydalanuvchi kaliti

PHP (Laravel, firebase/php-jwt)

PHP
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use Illuminate\Support\Facades\Http;

$iss = 'https://kitobchi.com/api/id';
abort_unless(hash_equals(session()->pull('kid_state', ''), (string) $request->query('state')), 403);
abort_unless($request->query('iss') === $iss, 403);

$tokens = Http::asForm()
    ->withBasicAuth(config('services.kitobchi.client_id'), config('services.kitobchi.secret'))
    ->post("$iss/token", [
        'grant_type'    => 'authorization_code',
        'code'          => $request->query('code'),
        'redirect_uri'  => route('kitobchi.callback'),
        'code_verifier' => session()->pull('kid_verifier'),
    ])->throw()->json();

$jwks = Cache::remember('kid_jwks', 900, fn () => Http::get("$iss/jwks")->throw()->json());
JWT::$leeway = 60;
$claims = (array) JWT::decode($tokens['id_token'], JWK::parseKeySet($jwks, 'RS256'));

abort_unless($claims['iss'] === $iss && $claims['aud'] === config('services.kitobchi.client_id'), 403);
abort_unless(hash_equals(session()->pull('kid_nonce', ''), $claims['nonce'] ?? ''), 403);

$user = User::firstOrCreate(['kitobchi_sub' => $claims['sub']], [/* ... */]);

Mobil ilovalar (Android / iOS / Flutter)

AppAuth kutubxonasidan foydalaning (Flutter: flutter_appauth). U discovery, PKCE va tizim brauzerini o'zi boshqaradi. Mobil ilova — public client: ilovaga client_secret joylashtirmang. WebView ichida kirish sahifasini ochish taqiqlanadi (foydalanuvchi manzil satrini ko'rmaydi, fishingga yo'l ochiladi).

Dart — flutter_appauth
final result = await const FlutterAppAuth().authorizeAndExchangeCode(
  AuthorizationTokenRequest(
    'kid_xxxxxxxxxxxxxxxxxxxxxxxx',
    'uz.hamkor.app:/oauth',
    issuer: 'https://kitobchi.com/api/id',
    scopes: ['openid', 'profile', 'phone', 'offline_access'],
  ),
);
// result.idToken, result.accessToken, result.refreshToken
// refresh tokenni flutter_secure_storage'da saqlang