Tokens
Clients authenticate with access tokens. Backends authenticate token-admin calls with a master token.
Formats
- Master token:
MT_...(keep on the server). Policy fields (name,description,tenant_prefix,allow_ip_masks) are stored with the token in Cockpit, not inside the secret string. - Access token:
AT_{token_id}_{secret}
token_id and secret are hex. Helpers parseAccessToken / parse_access_token split them. Never log the full string.
Lifetime
expires_atis ISO-8601 UTC.- Maximum TTL is 24 hours from server time. Longer values are rejected.
- After expiry, new connections fail.
- Existing sockets are not a forever lease: mint a new AT or refresh before expiry.
Refresh (backend, master token):
PUT /v1/refresh-token- Body uses
token_id(not the secret) and a newexpires_at(still max 24 hours from now). - Already expired tokens cannot be refreshed.
Revoke (backend, master token):
DELETE /v1/revoke-token- Body is the full
AT_...string.
SDKs wrap these as refreshAccessToken / refreshAccessTokenFromAt (JavaScript) and refresh_access_token / refresh_access_token_from_at (Rust). Revoke is revokeAccessToken / revoke_access_token.
Master token fields (Cockpit)
These sit next to the secret hash. They are not encoded in MT_....
| Field | Role |
|---|---|
name | Required short label in Cockpit (max 64). |
description | Comment (max 512). |
tenant_prefix | Namespace for this app. Empty = whole overlay. Several master tokens may share one prefix. Cockpit adds a trailing . if the prefix ends with a letter or digit (paint → paint.), so app does not match apple. On POST /v1/get-token the stored prefix is prepended to each tenant_ids value that does not already start with it. The access token then carries the physical name (paint. + lobby → paint.lobby). Clients must use that physical name on the socket. After prepend, tenant length must stay ≤ 128. |
allow_ip_masks | CIDR or exact IP. Limits who may call get-token, refresh, revoke, and list with this master token. Empty = no IP limit. If masks are set and the caller IP is unknown, the call is refused. This is not the access-token socket IP list. |
Do not copy channel grants, origin, protocol, or region lists onto the master token.
Permissions on the token
Each access token carries tenant_grants:
tenant_ids— exact tenant names (no wildcards)allow_channels_pub— publish rulesallow_channels_sub— subscribe rules
Optional extra rights:
allow_ip_masks— CIDR / exact IPallow_regions— currentlyUSandEUif your overlay uses themallowed_ws_origin— browser Origin allow-listallow_protocols—websocket/webtransportingress_kb_per_sec/egress_kb_per_sec— edge caps, typically 8–16384
Full pattern rules: Permissions model.
How the client sends the access token
WebSocket
Authorization: Bearer AT_...(server-side)Sec-WebSocket-Protocol: llps.v1, at.AT_...(browser)
The handshake echoes only llps.v1.
WebTransport
Bearer access token on the HTTP/3 session (SDK default).
What not to do
- Do not put master tokens in browsers, mobile apps, or source control.
- Do not log
MT_...orAT_.... - Do not mint 24-hour tokens for public web pages when 15–60 minutes will do.
- Do not grant
public.#publish to untrusted clients if you can grant one room instead.