Authentication
Kraty's two-layer auth model — SDK key authenticates the game, player secret authenticates the player. Plus the API-surface security boundary and key rotation guidance.
Kraty has three authentication surfaces, each with a different trust posture. Understanding which credential belongs where is the most important security decision you'll make integrating Kraty.
TL;DR
| Surface | Audience | Credential | What it can do |
|---|---|---|---|
/sdk/v1 | Game clients (mobile, desktop, web) | client_sdk API key + per-player secret | Drive a player's own attempts, claim rewards, spend wallet/inventory |
/server/v1 | Studio backend services | server_integration API key | Mint currency, grant items, issue manual grants, manage IAP fulfilment |
/admin/v1 | Portal staff (operators) | Member session (OAuth) | Configure events / catalog / API keys, audit logs, force-claim |
The cardinal rule: server_integration keys never touch a game
client. Anyone who can extract them owns every player's economy.
Layer 1 — SDK key (the game)
Every game in the portal has at least one client_sdk API key.
You bake one into the game build for each environment (test, live).
final kraty = Kraty(KratyClientOptions(
apiKey: '<your-client-sdk-key>',
));The key is sent as Authorization: Bearer <key> on every request.
It tells the backend "this request is from game X in studio Y, with
permission set Z." It does not tell the backend which player.
What's protected by SDK key alone
Public catalog data — anyone with the SDK key can read it without proving they're a specific player:
GET /sdk/v1/players/:id/events— list available eventsGET /sdk/v1/leaderboards/:id— snapshotGET /sdk/v1/leaderboards/:id/stream— SSE streamGET /sdk/v1/lobbies/:id— lobby state
This is intentional. A leaked SDK key shouldn't compromise player data, only let an attacker query game configuration.
Key rotation
Keys are revocable in the portal under
Game → Settings → API Keys. Revoking takes effect immediately.
Each key carries an environment (live or test) and a
permission set (client_sdk or server_integration).
Layer 2 — Player secret (the player)
Every player gets a per-player secret on first contact via
POST /sdk/v1/players/:externalId/register. The secret proves
"this request is genuinely from THIS player." Combined with the
SDK key, the server has the full identity it needs to authorise
mutations.
You almost never call players.register directly — every
official SDK lazily registers + persists the secret on the first
player-scoped call:
const kraty = new Kraty({ apiKey: '<your-client-sdk-key>' });
// First call below registers the player, mints the secret, and
// persists both. Subsequent calls reuse the persisted identity.
await kraty.events.listForPlayer();The same one-call setup ships in the Flutter and Unity SDKs. See each SDK's auth section for the persistence backend chosen on that platform and how to override it.
What's protected by SDK key + player secret
Anything that reads or mutates a specific player's state:
POST /sdk/v1/players/:id/events/:key/start(paysentryCost!)POST /sdk/v1/players/:id/events/:key/attempts/:id/progressGET /sdk/v1/players/:id/inventoryPOST /sdk/v1/players/:id/inventory/:key/consumeGET /sdk/v1/players/:id/walletPOST /sdk/v1/players/:id/wallet/:key/debitGET /sdk/v1/players/:id/pending-grantsPOST /sdk/v1/players/:id/grants/:id/claimPOST /sdk/v1/players/:id/crates/:id/open
Bob's secret on Carol's route → 401 player_secret_invalid. A
leaked SDK key with no per-player secret → 401 on all of the above.
Cross-player attack — what's blocked
attacker has: leaked SDK key (e.g. dumped from a competitor's APK)
attacker tries:
GET /players/legitimate_player/inventory → 401 player_secret_invalid
POST /players/legitimate_player/wallet/gold/debit → 401 player_secret_invalid
POST /players/legitimate_player/events/x/start → 401 player_secret_invalidThe attacker can still:
- Read the events catalog (
events.listForPlayeris not gated) - Read leaderboards
- Register a NEW player and play normally as themselves
Which is the right blast radius — the player gate prevents anyone from spending or reading another player's specific state.
Storage
Two-layer auth is only as strong as the device storage. Each SDK picks a sensible default for its platform out of the box:
| Platform | Default backend |
|---|---|
| Browser / React Native (TS SDK) | LocalStorageSecretStore (wraps window.localStorage) |
| Node / SSR / workers (TS SDK) | InMemorySecretStore (volatile — pass a custom store for durability) |
| Flutter mobile / desktop / web | SharedPreferencesSecretStore (wraps shared_preferences) |
| Unity | PlayerPrefsSecretStore (wraps UnityEngine.PlayerPrefs) |
For higher-value economies, plumb a custom SecretStore (browser:
sessionStorage or IndexedDB; Flutter: flutter_secure_storage;
Unity: a platform-specific keychain wrapper). Every SDK exposes
the interface and lets you pass an instance through the options
object — see the per-platform docs.
Recovery when a secret is lost
If a player wipes app data or switches devices, the local secret
is gone but the server still has the hash. Calling register
again returns 409 player_already_registered. You have two paths:
-
Production: surface "account recovery" UX — let the player log into a linked identity (Apple/Google/email), then have your studio backend call
/admin/v1/players/.../rotate-secretto issue a fresh secret which you hand back to the client. -
Dev / test environments: call
register?force=true— the backend rotates the secret in-place. Only accepted on non-liveAPI keys so a production-key leak can't be used to lock out every player.
The official SDKs surface the 409 through KratyApiError.isPlayerAlreadyRegistered.
For a production recovery flow, catch the error and run path (1).
In dev / test environments only, you can re-issue the secret by
hitting POST /sdk/v1/players/:id/register?force=true from your
own code — live API keys reject force.
Layer 3 — Server integration (your backend)
/server/v1 is the studio backend's call surface for fulfilment
operations that must NEVER reach the player's device:
POST /server/v1/players/:id/wallet/:key/credit— IAP fulfilmentPOST /server/v1/players/:id/wallet/:key/debit— refundsPOST /server/v1/players/:id/inventory/:key/grant— entitlementPOST /server/v1/players/:id/inventory/:key/revoke— chargebacksPOST /server/v1/players/:id/grants— manual server-issued grants
These bypass the player secret (your backend is trusted) but require
a server_integration API key and idempotency keys.
Never put a server_integration key in a game client. Not in
build configs, not in obfuscated strings, not on a build flag that
"only loads in dev." If it can reach the player's process, it can
be extracted. Use @kraty/server-sdk (Node) or
kraty-admin (Python) from a backend you
control. The Flutter and Unity SDKs deliberately don't expose
/server/v1 endpoints to make this hard to get wrong.
Correct production architecture:
┌─────────────┐ Authorization: Bearer <client_sdk key>
│ Game client │──── X-Player-Secret: <per-player> ──→ ┌────────┐
└─────────────┘ │ │
│ Kraty │
┌─────────────┐ │ │
│ Studio │──── Authorization: Bearer ─────────────→ │ │
│ backend │ <server_integration key> └────────┘
└─────────────┘
▲
│ HTTPS (your auth)
│
┌─────────────┐
│ Game client │
└─────────────┘Game client talks to Kraty for player flows. Studio backend talks
to Kraty for fulfilment / admin flows. Game client also talks to
studio backend for things like IAP receipt verification — the
studio backend validates the receipt with Apple/Google, then calls
/server/v1/.../wallet/credit on Kraty.
Layer 4 — Portal members (operators)
The portal (portal.kraty.io) authenticates studio members via
OAuth (Google today) and issues HTTP-only session cookies. All
/admin/v1 calls require a member session + permission grant.
This surface is never consumed by SDKs — it's the portal UI talking to its own backend. Operators use the portal to configure events, manage keys, run audits, force-claim stuck grants, etc.
Audit logging
Every credentialed write surfaces in audit logs:
client_sdkcalls →core.api_keys.last_used_at+ log of the operationserver_integrationcalls → audit log with the API key prefix- Portal member actions → audit log with the actor's member id
- Player register/rotate → audit row with the originating IP
Portal users can read the audit log under Studio → Audit Log scoped per game.
Quick checklist
Before shipping a Kraty integration to production, verify:
- Only
client_sdkkeys in game build outputs - Game build uses an
environment: 'live'key (test keys rejectforce=truerotations — see Recovery) - Player secret stored via platform secure storage (not just plain
SharedPreferencesfor high-value economies) - All IAP fulfilment goes via your backend with
server_integrationkey, not direct from the client - Account recovery flow defined for lost secrets (your backend can call admin rotate)
- Audit log alerts wired up for unusual
server_integrationkey activity