KukurukuID — OAuth 2.0 Integration
KukurukuID is an OAuth 2.0 / OpenID Connect authentication server. Merchants can add a "Login with KukurukuID" button to their websites, allowing Kukuruku users to authenticate seamlessly.
Prerequisites
Contact the Kukuruku team to receive your OAuth 2.0 credentials:
| Credential | Description |
|---|---|
client_id | Your OAuth 2.0 client identifier |
client_secret | Your OAuth 2.0 client secret (for confidential clients) |
redirect_uri | Approved callback URL(s) for your application |
Endpoints
Auth Server URL: https://auth.kukuruku.win
| Endpoint | URL | Method |
|---|---|---|
| Authorization | /realms/public/protocol/openid-connect/auth | GET |
| Token | /realms/public/protocol/openid-connect/token | POST |
| UserInfo | /realms/public/protocol/openid-connect/userinfo | GET |
| Logout | /realms/public/protocol/openid-connect/logout | POST |
| JWKS | /realms/public/protocol/openid-connect/certs | GET |
| Discovery | /realms/public/.well-known/openid-configuration | GET |
Supported Flows
| Flow | Use case |
|---|---|
| Authorization Code | Server-side applications (recommended) |
| Authorization Code + PKCE | Client-side / SPA applications |
Authorization Code Flow (Confidential Clients)
For server-side applications that can securely store a client_secret.
Step 1: Redirect to Login
GET {auth_url}/realms/public/protocol/openid-connect/auth
Redirect the user to KukurukuID's login page. After authentication, the user is redirected back to your redirect_uri with an authorization code.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | string | Yes | Your client identifier |
redirect_uri | string | Yes | Your registered callback URL |
response_type | string | Yes | Must be code |
scope | string | No | Space-separated scopes. Default: openid |
state | string | Recommended | Random string for CSRF protection |
Example:
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
After successful authentication, the user is redirected to:
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
Step 2: Exchange Code for Tokens
POST {auth_url}/realms/public/protocol/openid-connect/token
Exchange the authorization code for access and refresh tokens.
| Field | Type | Required | Description |
|---|---|---|---|
client_id | string | Yes | Your client identifier |
client_secret | string | Yes | Your client secret |
grant_type | string | Yes | Must be authorization_code |
code | string | Yes | Authorization code from Step 1 |
redirect_uri | string | Yes | Same redirect_uri as in Step 1 |
Response 200:
| Field | Type | Description |
|---|---|---|
access_token | string | JWT access token (5 min TTL) |
refresh_token | string | Token for obtaining new access tokens |
token_type | string | Always Bearer |
id_token | string | OpenID Connect ID token with user claims |
expires_in | integer | Access token lifetime in seconds |
scope | string | Granted scopes |
Authorization Code Flow with PKCE (Public Clients)
For client-side (SPA) applications where storing a client_secret is not possible.
Step 1: Generate Code Verifier and Challenge
Before redirecting, generate a random string (code verifier) and its SHA-256 hash (code challenge):
// Generate code_verifier (43-128 characters, URL-safe)
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const codeVerifier = btoa(String.fromCharCode(...array))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
// Generate code_challenge (SHA-256 hash of 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, '');
Step 2: Redirect to Login
GET {auth_url}/realms/public/protocol/openid-connect/auth
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | string | Yes | Your client identifier |
redirect_uri | string | Yes | Your registered callback URL |
response_type | string | Yes | Must be code |
code_challenge_method | string | Yes | Must be S256 |
code_challenge | string | Yes | SHA-256 hash of the code verifier |
scope | string | No | Space-separated scopes |
state | string | Recommended | Random string for CSRF protection |
Step 3: Exchange Code for Tokens
POST {auth_url}/realms/public/protocol/openid-connect/token
| Field | Type | Required | Description |
|---|---|---|---|
client_id | string | Yes | Your client identifier |
grant_type | string | Yes | Must be authorization_code |
code | string | Yes | Authorization code from Step 2 |
redirect_uri | string | Yes | Same redirect_uri as in Step 2 |
code_verifier | string | Yes | Original random string from Step 1 |
No client_secret is needed for public clients — the code_verifier proves the token request came from the same party that initiated the authorization.
The response format is identical to the Confidential Client response.
Refreshing Tokens
POST {auth_url}/realms/public/protocol/openid-connect/token
Access tokens expire after 5 minutes. Use the refresh token to obtain new tokens without requiring the user to log in again.
User Context API
Get Current User
GET /api/v1/me
Returns profile information about the authenticated user. This endpoint is on the Kukuruku API (https://api.kukuruku.win), not the auth server.
Response 200:
Response Fields
| Field | Type | Description |
|---|---|---|
id | string | User's unique identifier in Kukuruku |
first_name | string|null | First name |
middle_name | string|null | Middle name |
last_name | string|null | Last name |
without_middle_name | boolean | User has no middle name |
date_of_birth | string|null | Date of birth (YYYY-MM-DD) |
place_of_birth | string|null | Place of birth |
default_currency | string | User's default currency (RUB, USD, USDT) |
JWT Token Claims
The access token is a signed JWT (RS256) containing these claims:
| Claim | Type | Description |
|---|---|---|
sub | string | User UUID in Keycloak |
email | string | User's email address |
email_verified | boolean | Whether email is verified |
name | string | Full name |
given_name | string | First name |
family_name | string | Last name |
user_id | string | Kukuruku user ID (numeric) |
preferred_username | string | Username (usually email) |
iss | string | Issuer URL |
exp | integer | Token expiration (Unix timestamp) |
You can validate the JWT signature using the JWKS endpoint:
GET https://auth.kukuruku.win/realms/public/protocol/openid-connect/certs
Available Scopes
| Scope | Claims included |
|---|---|
openid | sub, auth_time |
profile | name, given_name, family_name |
email | email, email_verified |
phone | phone_number, phone_number_verified |
offline_access | Enables long-lived refresh tokens |
Example: Node.js Integration
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();
// Step 1: Redirect to login
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);
});
// Step 2: Exchange code for tokens
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;
// Step 3: Get user profile
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, etc.
// Create or find user in your system by user.id
});
Common Errors
| Error | Cause | Solution |
|---|---|---|
invalid_grant | Authorization code expired (>60 sec) or already used | Re-initiate the authorization flow |
invalid_client | Wrong client_secret | Verify your credentials |
unauthorized_client | Client not configured for this grant type | Contact Kukuruku team |
invalid_redirect_uri | redirect_uri not registered | Contact Kukuruku team to add your URI |