Kraty

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

SurfaceAudienceCredentialWhat it can do
/sdk/v1Game clients (mobile, desktop, web)client_sdk API key + per-player secretDrive a player's own attempts, claim rewards, spend wallet/inventory
/server/v1Studio backend servicesserver_integration API keyMint currency, grant items, issue manual grants, manage IAP fulfilment
/admin/v1Portal 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 events
  • GET /sdk/v1/leaderboards/:id — snapshot
  • GET /sdk/v1/leaderboards/:id/stream — SSE stream
  • GET /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 (pays entryCost!)
  • POST /sdk/v1/players/:id/events/:key/attempts/:id/progress
  • GET /sdk/v1/players/:id/inventory
  • POST /sdk/v1/players/:id/inventory/:key/consume
  • GET /sdk/v1/players/:id/wallet
  • POST /sdk/v1/players/:id/wallet/:key/debit
  • GET /sdk/v1/players/:id/pending-grants
  • POST /sdk/v1/players/:id/grants/:id/claim
  • POST /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_invalid

The attacker can still:

  • Read the events catalog (events.listForPlayer is 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:

PlatformDefault 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 / webSharedPreferencesSecretStore (wraps shared_preferences)
UnityPlayerPrefsSecretStore (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:

  1. 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-secret to issue a fresh secret which you hand back to the client.

  2. Dev / test environments: call register?force=true — the backend rotates the secret in-place. Only accepted on non-live API 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 fulfilment
  • POST /server/v1/players/:id/wallet/:key/debit — refunds
  • POST /server/v1/players/:id/inventory/:key/grant — entitlement
  • POST /server/v1/players/:id/inventory/:key/revoke — chargebacks
  • POST /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_sdk calls → core.api_keys.last_used_at + log of the operation
  • server_integration calls → 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_sdk keys in game build outputs
  • Game build uses an environment: 'live' key (test keys reject force=true rotations — see Recovery)
  • Player secret stored via platform secure storage (not just plain SharedPreferences for high-value economies)
  • All IAP fulfilment goes via your backend with server_integration key, 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_integration key activity