Sign in with Buko
Identity-only sign-in for websites and native applications. The protocol returns a short-lived, signed ID token. It does not issue access tokens, refresh tokens, client secrets for public apps, or permissions to read messages and contacts. It is not a full OAuth/OIDC provider; use this guide and the supplied adapters instead of assuming compatibility with an arbitrary OIDC discovery client.
Availability
Deployment, signing keys and the service feature gate must be enabled before integrations can run. Creating a draft does not grant public access. Only its owner can test a draft; public sign-in requires administrative approval.
- Manage applications
- Create an application
- Issuer / authorization center:
https://auth.buko.app - Public keys:
https://auth.buko.app/identity/jwks.json - Web SDK:
https://buko.app/sdk/sign-in-v1.mjs
Accounts, clients and keys belong to the selected product. Never exchange a Buko code with another product's service.
Register an application
Provide an application name (60 characters), description (500), HTTPS icon URL, HTTPS website URL, HTTPS privacy-policy URL and contact email. Contact email is visible to reviewers, not to users or other applications.
An account can create 5 applications. Each application supports one client for each of Web, iOS, Android and macOS. Platform clients have different client_id values but share the application's stable account identity. Different applications receive different account identifiers.
| Platform | Required configuration |
|---|---|
| Web | Up to 5 exact HTTPS Origins; optional exact full-page redirect URLs |
| iOS / macOS | Bundle ID, Apple Team ID; generated callback Scheme and optional registered callbacks |
| Android | Package name, signing-certificate SHA-256 fingerprint; generated callback Scheme and optional registered callbacks |
Each client supports up to 5 exact callback URLs. No wildcards, userinfo, fixed query parameters or fragments. Web redirects must belong to registered Origins. An Origin contains scheme, host and optional port, for example https://example.com; it does not contain a path. Localhost and IP hosts are not accepted. Use a controlled HTTPS development domain for testing.
For each configured Web / HTTPS callback host, add the TXT record displayed in the console, then click Verify DNS. Example:
Name: _buko-signin.example.com
Type: TXT
Value: copy the complete generated value from the console
Proofs are valid for 30 days when testing drafts or submitting for review. An approved snapshot remains published until replaced or suspended; proof expiry alone does not silently disable a reviewed production integration.
Saving changes updates the draft only. Submit the draft for review to publish changes to an approved application. Pending/rejected edits do not change the currently approved configuration. Sensitive edits and submission require a real sign-in within 10 minutes; ordinary SSO and QR login do not reset that timer.
Web: popup sign-in
Register your website Origin. No callback backend or redirect URL is needed for popup mode. Copy the SDK into your own build (recommended), or serve it from the published SDK URL with the appropriate CORS policy. The source package is packages/identity-signin-web; it has no runtime dependencies.
import { createSignIn } from './sign-in-v1.mjs';
const identity = createSignIn({
product: 'buko',
clientId: 'COPY_WEB_CLIENT_ID',
});
// Call directly in a click handler; do not await anything before opening it.
document.querySelector('#sign-in').addEventListener('click', async () => {
try {
const { id_token, claims } = await identity.signInWithPopup();
// Signature, issuer, audience, nonce and expiry have already been checked.
showSignedInUser({ issuer: claims.iss, subject: claims.sub });
} catch (error) {
if (error.code !== 'cancelled' && error.code !== 'access_denied') {
showSignInError(error.code);
}
}
});
The SDK retains PKCE/state/nonce in memory and checks message Origin, source window and state. The authorization window returns only code / error and state in a type: "identity.signin.result" message to the exact registered Origin. A blocked popup reports popup_blocked; do not automatically open a second authorization channel after a user refuses or cancels.
Do not apply a COOP policy that severs the popup's opener while expecting popup messages. Test deployed headers, Safari and popup blockers. If your site needs strong opener isolation, offer full-page redirect mode instead.
Web: optional full-page redirect
Register the complete callback URL, then:
// Initiating page:
await identity.startRedirect({ redirectUri: 'https://example.com/sign-in/return' });
// Callback page, before rendering any third-party analytics or remote content:
const { id_token, claims } = await identity.completeRedirect();
The SDK stores only short-lived transaction data in same-tab sessionStorage, consumes it once, removes code/state from the address bar, exchanges the code and verifies the ID token. It does not store an official messenger session or share cookies across domains. Callback pages should use Referrer-Policy: no-referrer.
Native applications
The Flutter adapter is in packages/identity-signin-flutter. It supports iOS and Android official-App authorization, with system-browser fallback after a confirmed launch failure. macOS uses the system authentication browser. The adapter's Apple browser path currently uses the generated custom Scheme; platform-native integrations can also use registered verified HTTPS callbacks.
final signIn = NiximSignIn(
product: IdentityProduct.buko,
clientId: 'COPY_PLATFORM_CLIENT_ID',
callbackUri: Uri.parse('COPY_GENERATED_CALLBACK_URL'),
);
final result = await signIn.signIn();
// result.claims has been verified; result.subject is app-specific.
// Explicit browser retry, if the user chooses it:
// await signIn.signIn(preferOfficialApp: false);
Register the generated Scheme in the receiving app. For iOS use CFBundleURLTypes / CFBundleURLSchemes in Info.plist. For Android register a VIEW + DEFAULT + BROWSABLE intent filter on your singleTop activity, restricted to your exact scheme and callback path; enable app_links handling and disable Flutter's competing automatic deep-link handler. For macOS register the Scheme in Info.plist and enable outgoing network access. No Apple Developer / Google Cloud entry is required merely to declare a custom Scheme. Verified HTTPS links need the platform's domain association files and entitlements instead.
The SDK directly launches the fixed official Android package or an iOS Universal Link with universalLinksOnly. It performs no installed-package query and adds no <queries>, QUERY_ALL_PACKAGES or LSApplicationQueriesSchemes declarations. Android browser fallback uses Custom Tabs; Apple uses ASWebAuthenticationSession. Use a cancel button wired to signIn.cancel() while waiting. Android browser closure does not reliably report cancellation; explicit cancellation or the bounded timeout ends that attempt. Process death discards the in-memory verifier; start again rather than accepting an unbound callback.
Custom Schemes are not exclusive ownership proofs. PKCE prevents a different app that intercepts the code from redeeming it without the verifier. Platform metadata and the public client ID are not App Attest / Play Integrity evidence.
Native protocol
All secrets below are independent random 32-byte values, encoded as unpadded base64url (43 characters). Generate them with a cryptographically secure source. code_challenge is base64url(SHA-256(verifier)); only S256 is supported.
POST https://auth.buko.app/identity/native/requests
Content-Type: application/json
{
"client_id": "COPY_PLATFORM_CLIENT_ID",
"response_type": "code",
"scope": "openid",
"response_mode": "query",
"destination": "COPY_REGISTERED_CALLBACK_URL",
"state": "RANDOM_43_CHARACTER_VALUE",
"nonce": "ANOTHER_RANDOM_43_CHARACTER_VALUE",
"code_challenge": "SHA256_VERIFIER_BASE64URL",
"code_challenge_method": "S256"
}
Response: {id, launch_ticket, requester_secret, expires_at}. Launch:
https://auth.buko.app/identity/open-app#request_id=ID&launch_ticket=TICKET
Keep requester_secret and verifier inside the requesting app. Never put them in links, analytics, logs or QR codes. The native launch ticket is valid for 120 seconds. Only the signed-in official phone can read the application details and approve or refuse this request. After approval the code gets a full 60-second redemption window. Status polling never returns the code or user identity.
POST /identity/native/requests/{id}/status and /cancel accept JSON {requester_secret}. Do not poll faster than 2 seconds. These endpoints do not accept a browser Origin. A browser authorization must be created through the authorization center, not by obtaining native approval secrets in JavaScript.
If the official App cannot be launched, cancel the native request first and create a new browser authorization with fresh state, nonce and verifier. Never fall back after refusal, cancellation or timeout. The native fallback landing page does not approve the native ticket in a browser.
Browser authorization parameters
Open https://auth.buko.app/identity/authorize with these query parameters:
| Parameter | Value |
|---|---|
| client_id | Registered platform client ID |
| response_type | code |
| scope | openid or omitted |
| response_mode | web_message for Web popup; query for registered redirect |
| destination | Exact registered Origin (popup) or full callback (redirect) |
| state | Random per-attempt 43-character base64url value |
| nonce | Independent random per-attempt 43-character base64url value |
| code_challenge | S256 challenge |
| code_challenge_method | S256 |
Browser requests last 10 minutes. Approval codes last at most 60 seconds, bounded by the request deadline. The user confirms every third-party authorization even when already signed in. Login/signup share the same entry; the service determines whether the account already exists. QR authentication first signs into the official center; it does not skip third-party confirmation.
Exchange the code
POST https://auth.buko.app/identity/token?client_id=COPY_PLATFORM_CLIENT_ID
Content-Type: application/json
{
"grant_type": "authorization_code",
"client_id": "COPY_PLATFORM_CLIENT_ID",
"response_mode": "query",
"destination": "COPY_REGISTERED_CALLBACK_URL",
"code": "RECEIVED_CODE",
"code_verifier": "ORIGINAL_VERIFIER"
}
Popup exchanges use response_mode: "web_message" and the original Origin as destination. The body and query client IDs must agree. Browser requests use JSON and exact-Origin CORS preflight; omit credentials. Native HTTP exchanges do not send a browser Origin. The code is atomically consumed once; concurrent redemption has one winner. If an exchange result is lost, start a new sign-in.
{ "id_token": "SIGNED_RS256_JWT", "id_token_expires_in": 300 }
Do not expect access_token, refresh_token or token_type.
Verify identity
Use the fixed product issuer's /identity/jwks.json; never follow a token's jku, x5u or arbitrary key URL. Accept only RS256 and the expected kid from these public keys. Verify the actual signature and all of:
issexactly equalshttps://auth.buko.app.audexactly equals the initiating platform client ID.nonceequals the initiating transaction's nonce.subis a nonempty, app-specific opaque identifier.iatandexpare integer seconds; lifetime is at most 300 seconds.- Current time is within the valid interval, with at most 60 seconds clock skew.
Store (iss, sub) as the account key. Do not use a nickname, handle, email or phone number as the unique identity. The same application gets the same subject across its platform clients; different applications get different subjects. There is no userinfo endpoint in this version.
An application may have no backend or existing account system: it can use the verified identity directly for local personalization. If it has protected server data, its server must verify the ID token and bind the nonce to its own login attempt before creating its own session; client-side verification does not protect a server from forged requests.
Errors and revocation
access_denied / cancelled mean the user stopped the attempt. invalid_grant means the code/request is expired, consumed, changed or no longer authorized. rate_limited requires backoff. identity_unavailable means the service gate or signing configuration is unavailable. Do not silently retry one-use exchanges.
Users can revoke an application's grant in Settings → Privacy and security → Authorized applications. Revocation invalidates unconsumed codes and requires a new approval. It cannot instantly erase already-issued offline-verifiable ID tokens or terminate a third party's own sessions. Apps and administrators can disable an integration; new approvals and exchanges then fail.
Implementation checklist
- Create the application and configure the correct platform client.
- Verify domains; test only with the owning account until approved.
- Register the exact callback or Origin and keep verifier/state/nonce per attempt.
- Validate full callback, state and JWT; do not trust decoded JSON alone.
- Test refusal, app absence, popup blockers, duplicate callbacks, expiry, account switching, revoked grants and network loss.
- Submit for review. Changes to published configuration require a new review.