Authentication
Every connection authenticates with a token in its first frame. kraken does not decide by itself whether a token is good: it asks its auth backend, caches the answer, and enforces the grants that come back on every subscribe and publish.
| Backend | Configure with | Tokens | Good for |
|---|---|---|---|
| Static file | AUTH_BACKEND=static (the default) | Any string you list in auth.json | Development, and small deployments with a fixed set of clients |
| Your own service | AUTH_BACKEND=http, AUTH_HTTP_URL | Whatever your service accepts | Plugging kraken into an existing user store |
@nolag/core | AUTH_BACKEND=http, AUTH_HTTP_URL pointing at core | Actor tokens and client tokens (JWTs) | The full model: projects, apps, rooms, grants, access scopes and signing keys |
The second and third are the same mechanism: @nolag/core is one implementation of kraken's HTTP auth contract.
Connecting with a token
The token is the first argument to the client. The SDK sends it in the auth frame as soon as the WebSocket opens.
import { NoLag } from "@nolag/js-sdk";
const client = NoLag("your_token", { url: "ws://localhost:8080/ws" });
await client.connect();
console.log("connected as", client.actorId);A refused token fails connect() with the reason kraken gave, for example access_denied when the backend said no, authentication_failed when an HTTP backend answered with an error status, connection_failed when kraken could not reach it, or token_expired for an expired client token. The Wire Protocol lists them all.
Static tokens
With the default backend, tokens are the keys of a JSON file, and each entry carries the actor's identity and its grants:
{
"tokens": {
"dev-token-bob": {
"actorTokenId": "bob",
"actorType": "user",
"allowedTopics": [
{ "pattern": "demo/general/#", "permission": "pubSub", "app_id": "demo-app", "app_name": "demo" }
]
}
}
}
A token can be any string, so use long random values. The file format, reloading and revocation are covered in Static Auth File.
Actor tokens (@nolag/core)
With @nolag/core, every identity that connects is an actor, and each actor has one access token:
at_live_<12 hex characters>.<43 base64url characters>
- The part before the dot is the actor's public key id (for example
at_live_43365b058a2e). It is safe to log and to store in your own database. - The part after the dot is the secret. Core stores only its SHA-256 hash, so the full token is shown once, when the actor is created, and cannot be recovered afterwards. Lose it and you create the actor again.
- Actors are created by importing a project document, which returns every minted token once, or by your own host through core's
ActorTokenFacade. - An actor has a status (
activeordisabled), an optional expiry, an optional access scope, and grants to apps and rooms.
After a successful connect, client.actorId holds the actor's id inside core (a UUID), not the key id.
Client tokens
Actor tokens are long-lived, so they belong on servers and devices you control. For a browser or a mobile app, your backend signs a short-lived JWT naming the actor, using a project signing key, and the client connects with that instead. The permissions are still the actor's. See Client Tokens.
Actor types
Every actor has a type. Core accepts seven:
actorType | Typical use |
|---|---|
device | Browsers, mobile apps, IoT hardware |
user | Authenticated end users |
service | Backend services |
session | Short-lived or temporary connections |
agent | Long-running workers |
orchestrator | Processes that hand work out to workers |
observer | Read-only monitoring connections |
Permissions come from grants, not from the type, with two exceptions:
- In
@nolag/core, a room type grant names an actor type, and gives every actor of that type access to the room. The quickstart'sviproom is private toserviceactors this way. - Core marks
agentandorchestratoractors as wanting a persistent broker session, with the project's session expiry. kraken passes that request only to the MQTT broker backend, as a non-clean MQTT session; what the external broker keeps for a disconnected session is then up to it. The defaultsynbroker ignores the request, so on a default kraken every actor type connects the same way.
With the static file, actorType is a label that defaults to user. kraken reports it back in the auth reply and otherwise ignores it.
Caching and revalidation
kraken keeps two kinds of state about a token, and both explain behaviour you will see:
A 30 second cache of successful validations, keyed on the token. A new connection with a token kraken validated in the last 30 seconds does not reach the backend. Refusals are not cached. So a token you add works at once, and a token you revoke can still open new connections for up to 30 seconds.
A copy of the grants in each connection, taken at connect time. Every subscribe and publish is checked against that copy, without calling the backend. The copy is refreshed by revalidation: when a connection sends a heartbeat (the SDKs send one every 30 seconds) and at least 10 minutes have passed since the last validation, kraken asks the backend again, by actor id.
- If the actor is still valid, its grants are replaced with the fresh ones, so rooms and grants added since it connected become usable.
- If the actor is no longer valid, kraken closes the connection with code
4001and the reason as the close reason. From@nolag/corethe reasons aretoken_not_found,token_revoked(the actor was disabled),token_expired,project_not_foundandscope_inactive. From the static file, an actor whose entry was removed getstoken_revoked. - If the backend cannot be reached or returns an error status, the connection stays up and kraken tries again at the next heartbeat.
- If the refreshed answer puts the connection over the connection limit for its
organization_id, kraken closes it with code4002.
So revoking an actor takes effect for live connections within about ten minutes, not immediately.
With an HTTP backend there is one shortcut, and @nolag/core implements it. A subscribe to a topic that is not in the connection's copy of the grants (typically a room created after the actor connected) makes kraken ask the backend once whether access is now allowed. A publish has no such shortcut: it fails with unknown_topic until the next revalidation or a reconnect. See the HTTP auth contract.
Transport security
kraken listens for plain ws:// and http:// only. Tokens travel in the first frame, so outside your own machine always put kraken behind a reverse proxy that terminates TLS, and connect clients with wss://. See the Production Checklist.
Good practice
- Keep long-lived tokens off untrusted clients. Static tokens and actor tokens go on servers and devices you control. Browsers and mobile apps get client tokens.
- Grant narrowly. Give each token only the topics it needs, with
subscribeorpublishrather thanpubSubwhere one direction is enough. - Rotate. With the static file, add the new token, move clients over, then delete the old entry. With core, create a new actor and disable the old one.
- Never run
AUTH_ALLOW_ALL=trueoutside local development. It accepts any token and grants every topic.