Static Auth File

With its default auth backend, static, kraken reads tokens and their grants from a JSON file. There is no database and no other service: change the file, and kraken picks the change up without a restart.

SettingDefaultMeaning
AUTH_BACKENDstaticSelects this backend
AUTH_FILE/app/examples/auth.jsonPath of the file inside the container. The repository's docker-compose.yml mounts ./examples/auth.json there, read-only.
AUTH_ALLOW_ALLfalseAccept any token with access to everything. Insecure; see allow-all mode.

Format

{
  "tokens": {
    "dev-token-alice": {
      "actorTokenId": "alice",
      "projectId": "demo-project",
      "organizationId": "demo-org",
      "actorType": "user",
      "allowedTopics": [
        {
          "pattern": "demo/general/#",
          "permission": "pubSub",
          "room_id": "room-general",
          "room_slug": "general",
          "app_id": "demo-app",
          "app_name": "demo"
        }
      ]
    }
  }
}

Each key under tokens is a token a client may connect with. Use long random strings: anyone who has one can connect as that actor. Keys outside tokens are ignored.

Token fields

FieldRequiredMeaning
actorTokenIdyesThe actor's id. The client sees it as actorTokenId in the auth reply and in presence; kraken uses it to revalidate and as the default load-balance group.
allowedTopicsThe grants. Without any, the token can connect but reach nothing.
projectIdReported back in the auth reply, and part of every load-balance group name.
organizationIdThe group kraken counts connections in for maxConnections.
actorTypeA label, user by default. See actor types.
maxConnectionsConnection limit for the token's organizationId, counted across every token that shares it; it has no effect without an organizationId. Absent means unlimited. A connect beyond the limit is refused with connection_limit_reached.
activeSubscriptionsSubscriptions kraken restores when this token reconnects. See restoring subscriptions.
allowedLobbiesLobbies the token may subscribe to, as [{ "lobby_slug": "...", "lobby_id": "..." }].
appId, appNameThe app every grant of this token belongs to. Default: the app_id of the first grant. See one app per token.
scopeSlugAn access scope slug. See Access Scopes.

The repository's example file also carries rateLimit. kraken ignores it: the limit is 50 publishes per second per connection for everyone. It ignores maxMessageSizeBytes too; the payload ceiling is a fixed 921,600 bytes. There is no way to configure webhooks in the static file.

Grant fields

Each entry in allowedTopics:

FieldMeaning
patternRequired. The addresses the grant covers, as app/room/topic. + matches exactly one segment and # matches everything after it, so demo/general/# covers every topic in room general of app demo, and demo/+/messages covers messages in every room.
permissionRequired. subscribe, publish or pubSub.
app_idThe app the grant belongs to. kraken uses it to name its internal topics, so tokens that should reach each other must agree on it.
app_nameDescriptive
room_id, room_slugNeeded for presence and lobbies. A client sends presence for a room by its slug; kraken finds the room through these two fields. Presence for a slug no grant names is silently ignored.
topicAn internal topic name for an exact (wildcard-free) pattern. Leave it out unless you know you need it; see below.

How grants turn into topics

kraken never uses your address as its internal topic directly. It resolves the address through the connection's grants, the same way for a subscribe and a publish:

  1. If a grant's pattern equals the address exactly and has a topic, that topic is the internal topic.
  2. Otherwise, if a grant matches the address (exactly, or with + and #), the internal topic is <app_id>/<address>.
  3. If no grant matches, the request fails with unknown_topic (42940).

So two tokens reach each other when they resolve the same address to the same internal topic. The simplest way to guarantee that is to leave topic out and use one app_id per app across the whole file. Then every token resolves demo/general/messages to demo-app/demo/general/messages, whether its grant is demo/general/#, demo/+/messages or the exact address. A token whose grant sets topic resolves that address somewhere else, and does not reach tokens whose grants do not.

One app per token

kraken puts all of a token's grants into one app, and gives every grant that app's id: the token's appId if it has one, otherwise the app_id of its first grant. A token whose grants span two apps therefore resolves the second app's addresses under the first app's id, and does not reach tokens that hold only the second app. Give such a token its own appId shared with the tokens it talks to, or use one app_id value for the whole file.

Restoring subscriptions

kraken does not remember what a client subscribed to. When a client reconnects (the SDKs send reconnect: true after an unexpected drop), kraken restores the subscriptions its auth backend lists for the actor, and for the static file that is activeSubscriptions:

"dev-token-carol": {
  "actorTokenId": "carol",
  "allowedTopics": [
    { "pattern": "demo/general/#", "permission": "pubSub", "app_id": "demo-app", "room_id": "room-general", "room_slug": "general" }
  ],
  "activeSubscriptions": ["demo/general/messages"]
}

We tested this against kraken v0.9.0: after a restart, a client using this token received messages on demo/general/messages again without subscribing a second time, while the demo tokens, which list no activeSubscriptions, received nothing until they subscribed again. Entries are addresses, or objects with pattern, loadBalance, loadBalanceGroup and filters. The list is fixed per token, whatever the client subscribed to; for anything else, subscribe in the client's connect handler, as in the Quick Start.

Reloading

kraken checks the file's modification time whenever it looks a token up, and re-reads the file when it has changed. No restart is needed:

  • A new token works on its first connection.
  • A changed grant reaches connections made with that token at their next revalidation, within about ten minutes. New connections get it at once, except that kraken may answer a connect from its 30 second cache.
  • A removed token is refused for new connections once kraken's 30 second cache of that token expires. Connections already open with it are closed with code 4001 and reason token_revoked at their next revalidation, within about ten minutes.

If the file stops being valid JSON, kraken logs Failed to parse auth file and refuses every token until the file is fixed. Check it with python3 -m json.tool auth.json or jq . auth.json before you save it into place.

On Docker Desktop we found that edits made on the host to a single-file bind mount (the repository's default, ./examples/auth.json:/app/examples/auth.json:ro) did not reach the container, so kraken kept serving the old tokens. Mounting the directory worked:

services:
  kraken:
    environment:
      - AUTH_FILE=/app/auth/auth.json
    volumes:
      - ./auth:/app/auth:ro

Rotating a token

  1. Add the new token as a second entry with the same actorTokenId.
  2. Move the client over to the new token.
  3. Delete the old entry.

While two entries share an actorTokenId, revalidation finds the actor through either of them, so connections made with the old token stay up until both entries are gone.

Allow-all mode

AUTH_ALLOW_ALL=true docker compose up -d

With AUTH_ALLOW_ALL=true, kraken accepts any token. The token string itself becomes the actor id, every actor gets pubSub on every topic (#), and the file is not read. kraken logs a line saying so for each token it accepts. It exists to try things out on your own machine and must never be set anywhere else: a broker started this way lets anyone who can reach it read and write everything.

When to move on

The static file suits a fixed set of services and devices. Move to an HTTP auth service or the full stack when you need tokens per user, short-lived browser tokens, changes without editing a file, or webhooks.