Sign in with KobKlein
OAuth 2.0 Authorization Code Flow · Authorization Code Flow with optional PKCE
Overview
"Sign in with KobKlein" lets your users log into your application using their KobKlein identity — verified KYC status, K-ID, and role — without your app ever touching a password. KobKlein acts as the identity provider (IdP) via standard OAuth 2.0 Authorization Code Flow.
Base URL
https://app.kobklein.com
Token URL
https://api.kobklein.com/v1/oauth/token
Userinfo
https://api.kobklein.com/v1/oauth/userinfo
Register an App
OAuth clients are created by KobKlein admins. Contact partners@kobklein.com or ask your account manager to provision a client ID + secret for your application. You will receive:
- client_id — public identifier, safe to include in URLs
- client_secret — keep server-side only, never in client bundles
- Approved redirect_uri(s) — must match exactly, including trailing slash
Token Exchange
After the user approves, KobKlein redirects to your redirect_uri with code and state. Exchange the code for an access token server-side:
// Node.js example
const response = await fetch("https://api.kobklein.com/v1/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "authorization_code",
code: req.query.code,
redirect_uri: "https://yourapp.com/callback",
client_id: process.env.KOBKLEIN_CLIENT_ID,
client_secret: process.env.KOBKLEIN_CLIENT_SECRET,
code_verifier: session.codeVerifier, // if using PKCE
}),
});
const { access_token, token_type, expires_in, scope } = await response.json();
// access_token starts with "kk_at_"
// expires_in: 3600 (1 hour)# Python example
import requests
resp = requests.post("https://api.kobklein.com/v1/oauth/token", json={
"grant_type": "authorization_code",
"code": request.args["code"],
"redirect_uri": "https://yourapp.com/callback",
"client_id": os.environ["KOBKLEIN_CLIENT_ID"],
"client_secret": os.environ["KOBKLEIN_CLIENT_SECRET"],
})
data = resp.json()
access_token = data["access_token"] # "kk_at_..."Token Response
{
"access_token": "kk_at_a3f9e1c7d2b8f0e4...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile kyc"
}Userinfo Endpoint
Fetch the authenticated user's profile using the access token:
curl https://api.kobklein.com/v1/oauth/userinfo \
-H "Authorization: Bearer kk_at_a3f9e1c7d2b8f0e4..."The response is scoped to what the user approved. Example with openid profile kyc:
{
"sub": "cuid2_user_id",
"kid": "KK-HT-00042819",
"name": "Jean Baptiste",
"handle": "@jeanbaptiste",
"role": "CLIENT",
"kycTier": 2,
"kycStatus": "approved",
"country": "HT",
"createdAt": "2025-11-14T09:22:00.000Z"
}Scopes
sub (cuid), kid (K-ID), role, country, createdAt
name, handle, avatarUrl (added on top of openid)
kycTier (0–3), kycStatus (approved/pending/rejected)
htgBalance, usdBalance (read-only, no history)
trustScore, trustTier, activeCredentials[]
Request only the scopes your app needs. The consent screen shows users exactly what each scope accesses.
PKCE (Recommended for SPAs & Mobile)
Use PKCE (Proof Key for Code Exchange) when you cannot safely store a client secret — single-page apps, mobile apps, or Electron apps.
// 1. Generate code verifier (server or client-side)
const codeVerifier = crypto.randomBytes(48).toString("base64url");
// 2. Compute S256 challenge
const codeChallenge = crypto
.createHash("sha256")
.update(codeVerifier)
.digest("base64url");
// 3. Include in authorization URL
const authUrl = new URL("https://app.kobklein.com/oauth/authorize");
authUrl.searchParams.set("code_challenge", codeChallenge);
authUrl.searchParams.set("code_challenge_method", "S256");
// ... other params
// 4. Include verifier in token exchange
fetch("https://api.kobklein.com/v1/oauth/token", {
method: "POST",
body: JSON.stringify({
grant_type: "authorization_code",
code,
redirect_uri,
client_id,
// no client_secret needed with PKCE!
code_verifier: codeVerifier,
}),
});Token Revocation
Revoke an access token (e.g., when the user disconnects your app from their KobKlein settings):
curl -X POST https://api.kobklein.com/v1/oauth/revoke \
-H "Content-Type: application/json" \
-d '{"token": "kk_at_a3f9e1c7d2b8f0e4..."}'
# 200 OK → { "ok": true }Error Reference
| error | HTTP | Cause |
|---|---|---|
| invalid_client | 401 | Bad client_id or client_secret |
| invalid_grant | 400 | Code expired (10 min TTL), already used, or PKCE mismatch |
| invalid_scope | 400 | Requested scope not in client's allowedScopes |
| redirect_uri_mismatch | 400 | redirect_uri doesn't match any registered URI |
| access_denied | 400 | User denied the authorization request |
| token_expired | 401 | Access token expired — request a new authorization |

