Это публичная документация для разработчиков сторонних приложений: игр, сайтов и других самостоятельных сервисов. Она описывает порядок подключения и требования к интеграции. Документация не содержит действующих закрытых ключей, паролей, пользовательских данных или сведений о зарегистрированных проектах.
1. Что делает КБП, а что — приложение
КБП на https://lk.kbp.kz — единственный источник учётной записи и пароля. Используем OpenID Connect (OIDC), Authorization Code Flow с PKCE S256. Центральный компонент — oidc-provider 9.12.2; пример приложения — openid-client 6.8.8 и jose 6.2.10. Протокол самостоятельно не переписывайте: используйте поддерживаемую OIDC-библиотеку своего языка.
Игра получает подтверждение личности и создаёт собственный профиль, прогресс, права и сессию. Пароль вводится только на сайте КБП. Приложение не получает административные права КБП, рекламные балансы, документы, БИН, телефон или дату рождения. SSO-токен не подходит для вызовов обычного API кабинета или админки.
Готовый центр и пример не означают, что ваша игра уже подключена. Для рабочего подключения нужны её точные адреса, открытые ключи, настройка в реестре и проверенная реализация отзыва на её сервере.
2. Как получить подключение
- Подготовьте серверное приложение на HTTPS. SPA или мобильный интерфейс использует собственный backend/BFF; закрытый ключ и токены не размещаются в JavaScript браузера, localStorage, мобильном бинарном файле или на Тильде.
- Создайте отдельную RSA-пару 3072 бит (допустимы 2048–4096), алгоритм RS256, уникальный
kid. В примере:node keys.mjs. Файлprivate/client-key.jsonостаётся на сервере приложения, режим 0600. Файлprivate/session-keyтакже закрытый. - Передайте главному администратору КБП только
client-public-jwks.json, название проекта, запрашиваемые scopes и точные URL из таблицы ниже. Открытый JWKS не секретен. Поляd,p,q,dp,dq,qiв передаваемом ключе недопустимы. - Главный администратор с действующей 2FA открывает https://admin.kbp.kz/sso-admin → «Единый вход → Проекты», создаёт подключение и сообщает
client_id.client_id— публичный идентификатор, не пароль. - Настройте сервер приложения, проведите проверки из раздела 13. После проверки администратор включает проект.
Вместо общего client_secret используется private_key_jwt: сервер приложения подписывает короткое одноразовое утверждение своим закрытым ключом. КБП проверяет его по зарегистрированному открытому JWKS. При таком методе КБП не выдаёт и не хранит секрет приложения; пароль аккаунта КБП для интеграции не нужен. Изображения экрана с ключами и содержимое закрытых файлов никому не отправляйте.
| Настройка проекта | Пример, заменить своим точным адресом |
|---|---|
| Возврат после входа (redirect_uri) | https://game.example.com/auth/kbp/callback |
| После выхода (post_logout_redirect_uri) | https://game.example.com/logged-out |
| Серверный отзыв (backchannel_logout_uri) | https://game.example.com/auth/kbp/backchannel-logout |
Все URL регистрируются целиком. Разный путь, query, регистр пути, завершающий слеш — другой адрес. Wildcard, префиксы, фрагменты, внутренние IP, localhost и динамические перенаправления в production запрещены. Адреса выхода относятся к origin зарегистрированных адресов входа. КБП проверяет публичный DNS серверного адреса отзыва и закрепляет проверенный IP на время запроса.
3. Адреса центра
Issuer: `https://lk.kbp.kz/api/sso/oidc` — сравнивается точно, без завершающего /.
Discovery: https://lk.kbp.kz/api/sso/oidc/.well-known/openid-configuration
Загружайте metadata из Discovery библиотекой, а не собирайте конечные адреса догадками. Для этой версии:
| Назначение | Адрес | Вызов |
|---|---|---|
| Начало входа | https://lk.kbp.kz/api/sso/oidc/auth | GET, браузер |
| Обмен кода / refresh | https://lk.kbp.kz/api/sso/oidc/token | POST form, сервер приложения |
| Открытые ключи центра | https://lk.kbp.kz/api/sso/oidc/jwks | GET |
| Данные личности | https://lk.kbp.kz/api/sso/oidc/me | GET, сервер с access token |
| Актуальность токена | https://lk.kbp.kz/api/sso/oidc/token/introspection | POST form, private_key_jwt |
| Отзыв токена | https://lk.kbp.kz/api/sso/oidc/token/revocation | POST form, private_key_jwt |
| Завершение текущей центральной сессии | https://lk.kbp.kz/api/sso/oidc/session/end | Браузерный OIDC RP-Initiated Logout |
Публичная самостоятельная регистрация OAuth-клиентов, implicit/password/client_credentials grant и plain PKCE не поддерживаются. API регистрации/редактирования проекта требует административной сессии КБП с 2FA; игровой токен туда не подходит.
4. Пошаговый вход
- Кнопка «Войти через КБП» ведёт на свой сервер
/login. - Сервер создаёт криптографически случайные
state,nonce,code_verifier(не меньше 32 байт случайности). Генерирует S256 challenge. Хранит значения только на сервере в одноразовом контексте, привязанном к HttpOnly cookie браузера, максимум 15 минут. - Сервер перенаправляет браузер на authorization_endpoint с
client_id, точнымredirect_uri,response_type=code,scope,state,nonce,code_challenge,code_challenge_method=S256. Дляoffline_accessобязательноprompt=consent. Для языка:ui_locales=ruилиkk. Вход идёт в обычной вкладке, без iframe и сторонних cookie. - При действующей сессии КБП повторный пароль не требуется. Иначе пользователь входит обычным способом. Новый пользователь регистрируется и подтверждает email. После подтверждения в том же браузере может продолжить вход в проект; просроченный/открытый на другом устройстве контекст требует снова начать вход из приложения. Даже старый аккаунт без подтверждённого email не получает SSO-доступ до подтверждения.
- КБП показывает название проекта и запрашиваемые данные. Пользователь продолжает или отказывается.
- Callback приложения однократно забирает и удаляет свой серверный контекст, сравнивает state и обменивает код на сервере, передавая исходный verifier и private_key_jwt. При несовпадении, ошибке/отказе или отсутствии cookie сессию не создаёт.
- OIDC-библиотека проверяет подпись ID Token по JWKS,
iss,audсвоего client_id,exp,iat,nonce, ожидаемый code flow и ответ. Включите проверку подписи ID Token и при прямом HTTPS-обмене; в примере этоenableNonRepudiationChecks. - При необходимости сервер запрашивает UserInfo и проверяет, что его
subсовпадает с ID Token. В примере, запрашивающем email, дополнительно проверяетсяemail_verified === true. - В транзакции создаётся/находится собственный профиль по
(issuer,sub)и создаётся новая случайная локальная сессия. Cookie: host-only, HttpOnly, Secure, SameSite=Lax, безDomain=.kbp.kz. Обычный callback не принимает пользовательскийreturnToна произвольный сайт. - Сразу уберите код из адресной строки редиректом на свою обычную страницу. Не сохраняйте код, токен или полную строку callback в access/error/analytics-логах.
Минимальное начало входа на Node.js (полный работающий код — в архиве):
import * as oidc from 'openid-client';
import { importJWK } from 'jose';
const key = await importJWK(privateJwk, 'RS256'); // закрытый файл на сервере
const config = await oidc.discovery(
new URL('https://lk.kbp.kz/api/sso/oidc'), clientId,
{ token_endpoint_auth_method: 'private_key_jwt', id_token_signed_response_alg: 'RS256' },
oidc.PrivateKeyJwt({ key, kid: privateJwk.kid }),
{ execute: [oidc.enableNonRepudiationChecks], timeout: 5 }
);
const verifier = oidc.randomPKCECodeVerifier();
const state = oidc.randomState(), nonce = oidc.randomNonce();
// Сохранить verifier/state/nonce на сервере и связать с одноразовой cookie.
const url = oidc.buildAuthorizationUrl(config, {
redirect_uri: registeredCallback,
response_type: 'code', scope: 'openid profile email offline_access', prompt: 'consent',
state, nonce, code_challenge: await oidc.calculatePKCECodeChallenge(verifier),
code_challenge_method: 'S256', ui_locales: 'ru'
});
// Ответ своего сервера: redirect 303 на url.href.Обработка ответа библиотекой:
const tokens = await oidc.authorizationCodeGrant(config, callbackUrl, {
pkceCodeVerifier: saved.verifier, expectedState: saved.state,
expectedNonce: saved.nonce, idTokenExpected: true
});
const identity = tokens.claims();
const info = await oidc.fetchUserInfo(config, tokens.access_token, identity.sub);
// Проверки должны закончиться ДО INSERT профиля и сессии.Не заменяйте проверку подписи простым JSON.parse JWT. ID Token — подтверждение входа, а не способ бесконечно считать аккаунт активным.
5. Данные и scopes
| Scope | Данные / действие |
|---|---|
openid (обязателен) | sub: постоянный непрозрачный идентификатор пользователя |
profile | name: имя из профиля КБП |
email | email, email_verified: true |
offline_access | Разрешает серверное обновление через refresh token |
Клиент может запросить только scopes, разрешённые в его карточке. Лишний scope отклоняется. Технические поля OIDC (iss, aud, iat, exp, nonce, sid) обеспечивают проверку, не являются пользовательскими правами. sid связывает центральную сессию с уведомлением о выходе; сохраните его в локальной сессии.
Для минимальной идентификации достаточно openid; имя и email запрашивайте по необходимости. Предлагаемый пример использует все четыре scopes, поэтому администратор должен разрешить их именно этому примеру. В локальном приложении email и name — обновляемые атрибуты, а не ключ личности. Нельзя автоматически объединять разные (issuer,sub) по совпавшей почте.
CREATE UNIQUE INDEX game_profiles_identity ON game_profiles(issuer,subject);
-- Атомарный INSERT ... ON CONFLICT(issuer,subject) ... RETURNING id.Смена email не создаёт второго профиля и не переносит прогресс. sub не меняется. КБП не принимает игровые пароли и не управляет игровой базой.
6. Сроки и хранение
| Объект | Максимальный срок |
|---|---|
| Контекст входа / подтверждение перехода | 15 минут, связан с браузером |
| Authorization code | 60 секунд, однократно |
| Access Token | 5 минут |
| ID Token | 5 минут |
| Refresh Token / разрешение проекта | 30 дней от первоначальной выдачи, без бесконечного продления |
| Центральная OIDC-сессия | 14 дней |
| Локальная сессия примера | 8 часов, собственная cookie |
| Кэш успешной introspection в приложении | Не более 30 секунд от начала проверки |
| Предельная потеря доступа при отзыве | 60 секунд для корректно подключённого проекта |
Обычная сессия кабинета КБП остаётся прежней: это отдельная cookie и прежние правила входа, 2FA и выхода. Истечение локальной сессии требует нового входа; refresh не должен восстанавливать уже отозванную локальную сессию.
На сервере приложения храните токены зашифрованными, отдельным ключом вне базы/репозитория. В примере AES-256-GCM с отдельным случайным nonce на запись и проверкой AAD. Cookie содержит только случайный идентификатор; в БД — его hash. Не помещайте токены в URL, HTML, письма, Telegram, логи, аналитику или localStorage.
7. Проверка актуальности — обязательная часть
Подписи ID Token недостаточно для выполнения игровых действий. Перед каждым защищённым запросом приложение проверяет собственную неотозванную сессию и результат introspection. Успешный ответ можно кэшировать максимум 30 секунд, считая время с начала запроса. Проверяйте active === true, client_id === ваш client_id, sub === sub сессии.
const status = await oidc.tokenIntrospection(config, accessToken);
if (status.active !== true || status.client_id !== clientId || status.sub !== session.subject) {
// Отозвать локальную сессию, запретить защищённое действие, предложить новый вход.
}При блокировке аккаунта, удалении, сбросе/смене пароля, потере подтверждения email, общем выходе или отключении проекта центральная проверка сразу перестаёт подтверждать старый доступ. Разблокировка не оживляет старые токены и сессии.
Если КБП недоступен, сетевой таймаут или ответ не проверен, после окончания допустимого кэша действие запрещается. Нельзя продлевать кэш при ошибке или продолжать игру по одному ID Token. В примере сессия при такой ошибке завершается; проект может вместо этого временно закрыть защищённые действия, сохранив данные, но не разрешать их без актуальной проверки.
Именно приложение обеспечивает этот предел для своих API, WebSocket-сообщений и фоновых пользовательских действий. Для долгого WebSocket проверяйте доступ периодически не реже 30 секунд и перед изменением защищённых данных. Центр не может закрыть произвольную сессию игры, которая игнорирует отзыв.
8. Серверный выход Back-Channel Logout
Зарегистрированный HTTPS endpoint приложения принимает POST application/x-www-form-urlencoded, поле logout_token. Cookie браузера и Origin здесь не нужны: это подписанное серверное уведомление. Исключение Origin ограничьте только этим endpoint, не всем API.
Используйте JWT-библиотеку. Проверяются RS256, подпись по JWKS КБП, точный issuer, audience своего клиента, обязательные iat, jti, events, событие http://schemas.openid.net/event/backchannel-logout, отсутствие nonce, наличие sid и/или sub. Пример принимает токен возрастом до 120 секунд с допуском часов 5 секунд. Часы серверов должны быть синхронизированы.
В одной транзакции зарегистрируйте уникальный (issuer,jti) и отзовите совпадающие локальные сессии по issuer + sid, при наличии sub также проверяя его. Если sid отсутствует, завершите локальные сессии этого sub. Повторная доставка того же токена должна вернуть успешный 200/204 и не удалять профиль/прогресс. Недействительная подпись или чужой audience — 400/401 без изменений.
КБП хранит очередь и историю доставки. Первая попытка обычно в течение 5 секунд; сетевой таймаут 5 секунд; до 12 попыток с интервалами 5,10,20,40,80,160, затем 300 секунд. Каждая попытка получает свежий подписанный токен. История доступна главному администратору в карточке проекта; завершённые записи технической доставки хранятся 90 дней, общий безопасный аудит — по политике КБП. Очередь не заменяет introspection: при потере всех уведомлений предел отзыва сохраняется через проверку актуальности.
9. Три разных действия выхода
- «Выйти» в игре: завершить только текущую локальную игровую сессию (POST с Origin + CSRF), очистить свою cookie. Другие проекты и кабинет продолжают работать. Не вызывайте центральный общий выход и не отзывайте общий grant при каждом локальном выходе.
- RP-Initiated Logout: стандартный
end_session_endpointиз Discovery с провереннымid_token_hint, зарегистрированнымpost_logout_redirect_uriи state, который проверяется приложением при возврате. КБП запрашивает подтверждение. Завершается текущая центральная OIDC-сессия и связанные разрешения; это не команда «все устройства». Обычная cookie кабинета не переопределяется этим протоколом. - «Выйти из всех сервисов на всех устройствах» в профиле КБП: отдельная явно подтверждаемая команда; отзывает все сессии кабинета и центральные разрешения этого пользователя на всех устройствах, ставит уведомления проектам. Игра должна обработать их и/или закрыть доступ по introspection. Данные сохраняются.
Обычная кнопка выхода из кабинета КБП сохраняет прежний смысл и не превращается в незаметный общий выход.
10. Refresh, отзыв и ошибки
offline_access нужен только если серверу действительно требуется refresh. Access token можно обновить через oidc.refreshTokenGrant(config, refreshToken). Refresh token меняется при каждом обмене; сохраняйте новую пару атомарно, сериализуйте обновление одной локальной сессии. Пример удерживает блокировку строки локальной сессии на время ограниченного 5-секундным таймаутом обмена; используйте отдельную БД приложения и разумные таймауты блокировок.
Повтор старого refresh token отзывает всю семью разрешения, включая выданные при ротации токены. invalid_grant → закрыть локальную сессию и начать новый вход. Не повторяйте бесконечно обмен старого кода/refresh. 30 дней отсчитываются от первоначальной выдачи, а не от каждого refresh.
Для отдельного явного отключения доступа приложения используйте token_revocation_endpoint библиотекой (oidc.tokenRevocation), затем завершите локальную сессию. Это отличается от обычного локального выхода.
| Ситуация | Действие приложения |
|---|---|
access_denied | Показать отказ и кнопку повторного входа, не создавать профиль/сессию |
| Нет контекста / неверные state или nonce | Отклонить callback; новый вход из приложения |
invalid_grant | Код/refresh больше не использовать, войти заново |
invalid_client | Закрыть операцию, проверить client_id/JWKS/kid/подпись/часы на сервере |
invalid_scope | Исправить запрос под разрешённые scopes |
Introspection active:false | Отозвать локальную сессию и запретить действие |
| Недоступность/429/5xx | Без обхода проверки, без продления кэша, без вывода токенов в ошибку |
11. Смена ключей
Ключи приложения: создаёт и хранит приложение. Новый открытый JWKS администратор сохраняет в карточке проекта. Изменение подключения (в том числе ключей/адресов/scopes) отзывает старый доступ только этого проекта; пользователи входят заново. Закрытый ключ не передаётся. Отключение проекта не блокирует пользователя и другие приложения.
Ключи подписи центра: kid может меняться. Используйте Discovery/JWKS и стандартное обновление ключей при неизвестном kid. Обычная ротация двухэтапная: новый ключ сначала появляется в JWKS, прежний продолжает подписывать; после минимум 6 минут (окно обновления кэша JWKS до 5 минут) новый становится активным. Приложение должно обновлять кэш JWKS не реже 5 минут. Центр сохраняет прежний открытый ключ минимум 24 часа; закрытый старый ключ не используется для новых токенов. Действующие токены 5 минут продолжают проверяться, сами refresh-семьи не продлеваются. Не закрепляйте единственный ключ навсегда и не отключайте проверку подписи при смене kid.
При компрометации ключа действует аварийная процедура: отзыв затронутого доступа и удаление скомпрометированного ключа, без обещания совместимости старых токенов. Это отдельная административная операция, не автоматическое действие подключённой игры. Ключ шифрования хранилища и cookie-подписи не заменяйте файлом другой среды.
12. Запуск рабочего примера
Архив: https://lk.kbp.kz/docs/kbp-sso-client.zip Markdown этой инструкции: https://lk.kbp.kz/docs/kbp-sso-integration.md
Состав: app.mjs, server.mjs, keys.mjs, schema.sql, .env.example, package.json, lockfile и README. Пример не содержит игры, платежей и готовых действующих ключей. Это серверное приложение с отдельными PostgreSQL-профилями и сессиями.
- Node.js 22.23+; PostgreSQL для собственной БД приложения. Установите зависимости
pnpm install --frozen-lockfile(либо ваш проверенный менеджер). - Выполните
schema.sqlв своей БД, не в базе аккаунтов КБП. node keys.mjs; настройте закрытый.envпо.env.example; выдайте секретным файлам 0600, каталогу private — 0700.- Передайте открытый JWKS и точные URL администратору, получите свой client_id и разрешённые scopes.
- Запустите
pnpm startза HTTPS reverse proxy своего домена. Не публикуйте private/.env/логи callback. Cookie принадлежат только своему host. - Откройте
/login, пройдите вход;/meпоказывает отдельный профиль. Локальный выход не завершает другие сервисы. - Проверьте блокировку/потерю уведомления/отзыв/дубли на искусственном тестовом аккаунте в согласованной среде. В production не создавайте фиктивные игровые платежи или массовые аккаунты.
Локальный HTTP разрешён только при явном SSO_LOCAL_TEST=true, NODE_ENV != production, обоих origin на http://127.0.0.1:<port> и отдельном локальном центре с отдельными ключами/БД. Рабочий центр КБП не регистрирует localhost. Не включайте insecure-режим для production-issuer или реальных аккаунтов.
Пример — основа интеграции, не готовая игровая платформа: добавьте свои игровые правила, права, API, ограничения частоты, обработку инфраструктурных ошибок, резервирование и очистку истёкших локальных контекстов/сессий/jti. Не удаляйте профили и прогресс при обычном выходе или блокировке.
13. Приёмка приложения разработчиком
Перед подключением должны проходить: проверенный вход; повторный вход без второго профиля; регистрация/email и восстановление с возвратом; отказ неподтверждённому/блокированному аккаунту; точные redirect; неверные state/nonce/PKCE; истёкший и повторный код; чужой client_id/audience; смена email без потери профиля; refresh rotation/replay; подпись и дубли logout; блокировка открытого приложения ≤60 секунд; потеря back-channel с тем же пределом; недоступность центра после 30-секундного кэша; локальный выход без выхода других проектов; общий выход без удаления прогресса; отключение одного клиента; ротация JWKS; мобильный интерфейс и язык.
Проверка центра и эталонного клиента не заменяет приёмку будущей игры. Разработчик игры отвечает за охват всех защищённых действий актуальной проверкой, собственные роли, профиль, прогресс, покупки, резервные копии, логи и безопасность своего сервера.
14. Источники стандартов
- OIDC Core: https://openid.net/specs/openid-connect-core-1_0.html
- Discovery: https://openid.net/specs/openid-connect-discovery-1_0.html
- OAuth Security BCP: https://www.rfc-editor.org/rfc/rfc9700.html
- Introspection: https://www.rfc-editor.org/rfc/rfc7662.html
- Revocation: https://www.rfc-editor.org/rfc/rfc7009.html
- Back-Channel Logout: https://openid.net/specs/openid-connect-backchannel-1_0.html
- RP-Initiated Logout: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
- Библиотеки: https://github.com/panva/node-oidc-provider и https://github.com/panva/openid-client