KukurukuID — интеграция OAuth 2.0
KukurukuID — это сервер аутентификации OAuth 2.0 / OpenID Connect. Мерчанты могут добавить кнопку «Войти через KukurukuID» на свои сайты, позволяя пользователям Kukuruku аутентифицироваться без создания новых аккаунтов.
Предварительные требования
Свяжитесь с командой Kukuruku для получения OAuth 2.0 учётных данных:
| Учётные данные | Описание |
|---|---|
client_id | Идентификатор OAuth 2.0 клиента |
client_secret | Секрет OAuth 2.0 клиента (для конфиденциальных клиентов) |
redirect_uri | Одобренный callback URL для вашего приложения |
Эндпоинты
URL сервера аутентификации: https://auth.kukuruku.win
| Эндпоинт | URL | Метод |
|---|---|---|
| Авторизация | /realms/public/protocol/openid-connect/auth | GET |
| Токен | /realms/public/protocol/openid-connect/token | POST |
| Информация о пользователе | /realms/public/protocol/openid-connect/userinfo | GET |
| Выход | /realms/public/protocol/openid-connect/logout | POST |
| JWKS | /realms/public/protocol/openid-connect/certs | GET |
| Discovery | /realms/public/.well-known/openid-configuration | GET |
Поддерживаемые flows
| Flow | Применение |
|---|---|
| Authorization Code | Серверные приложения (рекомендуется) |
| Authorization Code + PKCE | Клиентские / SPA приложения |
Authorization Code Flow (конфиденциальные клиенты)
Для серверных приложений, которые могут безопасно хранить client_secret.
Шаг 1: Редирект на логин
GET {auth_url}/realms/public/protocol/openid-connect/auth
Перенаправьте пользователя на страницу входа KukurukuID. После аутентификации пользователь будет перенаправлен обратно на ваш redirect_uri с кодом авторизации.
Параметры запроса:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
client_id | string | Да | Идентификатор вашего клиента |
redirect_uri | string | Да | Зарегистрированный callback URL |
response_type | string | Да | Должен быть code |
scope | string | Нет | Scope'ы через пробел. По умолчанию: openid |
state | string | Рекомендуется | Случайная строка для защиты от CSRF |
Пример:
https://auth.kukuruku.win/realms/public/protocol/openid-connect/auth
?client_id=your-client-id
&redirect_uri=https://your-site.com/callback
&response_type=code
&scope=openid profile email
&state=random-state-string
После успешной аутентификации пользователь перенаправляется на:
https://your-site.com/callback
?code=c2a6dbb6-0640-4358-a047-8f87bde1d0ab.69eaf4bf-...
&session_state=69eaf4bf-58da-45e7-8ae7-92e6155e1ff8
&iss=https://auth.kukuruku.win/realms/public
&state=random-state-string
Шаг 2: Обмен кода на токены
POST {auth_url}/realms/public/protocol/openid-connect/token
Обменяйте код авторизации на access и refresh токены.
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
client_id | string | Да | Идентификатор вашего клиента |
client_secret | string | Да | Секрет вашего клиента |
grant_type | string | Да | Должен быть authorization_code |
code | string | Да | Код авторизации из Шага 1 |
redirect_uri | string | Да | Тот же redirect_uri, что и в Шаге 1 |
Ответ 200:
| Поле | Тип | Описание |
|---|---|---|
access_token | string | JWT access token (TTL 5 минут) |
refresh_token | string | Токен для получения нового access token |
token_type | string | Всегда Bearer |
id_token | string | OpenID Connect ID токен с claims пользователя |
expires_in | integer | Время жизни access token в секундах |
scope | string | Выданные scope'ы |
Authorization Code Flow с PKCE (публичные клиенты)
Для клиентских (SPA) приложений, где хранение client_secret невозможно.
Шаг 1: Генерация Code Verifier и Challenge
Перед редиректом сгенерируйте случайную строку (code verifier) и её SHA-256 хеш (code challenge):
// Генерация code_verifier (43-128 символов, URL-safe)
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const codeVerifier = btoa(String.fromCharCode(...array))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
// Генерация code_challenge (SHA-256 хеш verifier)
const digest = await crypto.subtle.digest('SHA-256',
new TextEncoder().encode(codeVerifier));
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
Шаг 2: Редирект на логин
GET {auth_url}/realms/public/protocol/openid-connect/auth
Параметры запроса:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
client_id | string | Да | Идентификатор клиента |
redirect_uri | string | Да | Зарегистрированный callback URL |
response_type | string | Да | Должен быть code |
code_challenge_method | string | Да | Должен быть S256 |
code_challenge | string | Да | SHA-256 хеш code verifier |
scope | string | Нет | Scope'ы через пробел |
state | string | Рекомендуется | Случайная строка для CSRF защиты |
Шаг 3: Обмен кода на токены
POST {auth_url}/realms/public/protocol/openid-connect/token
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
client_id | string | Да | Идентификатор клиента |
grant_type | string | Да | Должен быть authorization_code |
code | string | Да | Код авторизации из Шага 2 |
redirect_uri | string | Да | Тот же redirect_uri, что и в Шаге 2 |
code_verifier | string | Да | Исходная случайная строка из Шага 1 |
Для публичных клиентов client_secret не нужен — code_verifier подтверждает, что запрос токена исходит от того же приложения, которое инициировало авторизацию.
Формат ответа идентичен ответу для конфиденциальных клиентов.
Обновление токенов
POST {auth_url}/realms/public/protocol/openid-connect/token
Access токены истекают через 5 минут. Используйте refresh token для получения новых токенов без повторной аутентификации пользователя.
User Context API
Получить текущего пользователя
GET /api/v1/me
Возвращает информацию о профиле аутентифицированного пользователя. Этот эндпоинт находится на API Kukuruku (https://api.kukuruku.win), а не на сервере аутентификации.
Ответ 200:
Поля ответа
| Поле | Тип | Описание |
|---|---|---|
id | string | Уникальный идентификатор пользователя в Kukuruku |
first_name | string|null | Имя |
middle_name | string|null | Отчество |
last_name | string|null | Фамилия |
without_middle_name | boolean | Пользователь без отчества |
date_of_birth | string|null | Дата рождения (YYYY-MM-DD) |
place_of_birth | string|null | Место рождения |
default_currency | string | Валюта по умолчанию (RUB, USD, USDT) |
Claims в JWT токене
Access token — подписанный JWT (RS256) со следующими claims:
| Claim | Тип | Описание |
|---|---|---|
sub | string | UUID пользователя в Keycloak |
email | string | Email пользователя |
email_verified | boolean | Подтверждён ли email |
name | string | Полное имя |
given_name | string | Имя |
family_name | string | Фамилия |
user_id | string | Идентификатор в Kukuruku (числовой) |
preferred_username | string | Имя пользователя (обычно email) |
iss | string | URL издателя |
exp | integer | Время истечения (Unix timestamp) |
Вы можете проверить подпись JWT через JWKS эндпоинт:
GET https://auth.kukuruku.win/realms/public/protocol/openid-connect/certs
Доступные scope'ы
| Scope | Включённые claims |
|---|---|
openid | sub, auth_time |
profile | name, given_name, family_name |
email | email, email_verified |
phone | phone_number, phone_number_verified |
offline_access | Долгоживущие refresh токены |
Пример: интеграция на Node.js
const express = require('express');
const axios = require('axios');
const AUTH_URL = 'https://auth.kukuruku.win/realms/public';
const CLIENT_ID = 'your-client-id';
const CLIENT_SECRET = 'your-client-secret';
const REDIRECT_URI = 'https://your-site.com/callback';
const app = express();
// Шаг 1: Редирект на логин
app.get('/login', (req, res) => {
const state = crypto.randomUUID();
const url = `${AUTH_URL}/protocol/openid-connect/auth` +
`?client_id=${CLIENT_ID}` +
`&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
`&response_type=code` +
`&scope=openid+profile+email` +
`&state=${state}`;
res.redirect(url);
});
// Шаг 2: Обмен кода на токены
app.get('/callback', async (req, res) => {
const { code } = req.query;
const tokenResponse = await axios.post(
`${AUTH_URL}/protocol/openid-connect/token`,
new URLSearchParams({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: 'authorization_code',
code,
redirect_uri: REDIRECT_URI,
}),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
const { access_token } = tokenResponse.data;
// Шаг 3: Получение профиля пользователя
const userResponse = await axios.get(
'https://api.kukuruku.win/api/v1/me',
{ headers: { Authorization: `Bearer ${access_token}` } }
);
const user = userResponse.data.data;
// user.id, user.first_name, user.email и т.д.
// Найти или создать пользователя в вашей системе по user.id
});
Частые ошибки
| Ошибка | Причина | Решение |
|---|---|---|
invalid_grant | Код авторизации истёк (>60 сек) или уже использован | Повторно инициировать flow авторизации |
invalid_client | Неверный client_secret | Проверьте учётные данные |
unauthorized_client | Клиент не настроен для этого grant type | Обратитесь к команде Kukuruku |
invalid_redirect_uri | redirect_uri не зарегистрирован | Обратитесь к команде Kukuruku для добавления URI |