Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Permissions model

Tokens do two things:

  1. Authenticate the client (prove “who you are”).
  2. Authorize the client (what tenants and channels you can use).

tenant_grants

Permissions are described as a list of grants:

  • tenant_grants: a list of rules
    • tenant_ids: exact tenant names the grant applies to
    • allow_channels_pub: which channels are allowed for publish
    • allow_channels_sub: which subscription patterns the client is allowed to send

One token may have several grants (different tenant sets and different channel sets).

Full JSON shape and examples are in Client configuration.

Publish rules (allow_channels_pub) – pub-token rules

Publish rules use pub-token rules – a syntax similar to sub-token rules but without ? and *. A pub-token rule describes which concrete channels the client is allowed to publish to.

A pub-token rule is a dot-separated list of segments. Each segment is one of:

SegmentMeaning
storeExact literal. The channel must have this exact segment.
(a|b|prefix*)Alternatives. The channel segment must match at least one variant. Prefix variants like b* match any segment starting with b.
#Tail (0+). Only as the last segment. Allows any number of additional channel segments (including zero).
>Tail (1+). Only as the last segment. Allows one or more additional channel segments.

Not allowed in publish rules: ? (any literal) and * (wildcard). These are only available in subscribe rules.

Matching

When a client publishes to a channel, the channel name is always a concrete string (no wildcards). The server checks each segment of the channel name against the pub-token rule:

Rule segmentChannel segment must be
Literal "store"Exactly "store"
Alternatives (eu|us|a*)A value matching at least one variant
# (tail)0 or more additional segments
> (tail)1 or more additional segments

Examples

Exact rule: store.sell.status – only store.sell.status is allowed.

Prefix tree: store.sell.# – allows store.sell, store.sell.status, store.sell.status.v2, etc.

Alternatives: orders.(eu|us|a*).#

ChannelAllowed?Why
orders.euyesmatches eu
orders.us.westyesmatches us, tail allowed
orders.asia.eastyesmatches a* (starts with a)
orders.runonot in alternatives

Tail > vs #:

  • events.# allows: events, events.click, events.click.v2
  • events.> allows: events.click, events.click.v2 but not events

Invalid publish rules (rejected at token creation)

  • store.?.status? not allowed in publish rules
  • store.*.status* not allowed in publish rules
  • store.#.status# not at end
  • store..sell – empty segment

Limits

Same as subscribe rules: max 32 segments, max 128 bytes per segment, max 16 alternatives.

Subscribe rules (allow_channels_sub) – sub-token rules

Subscribe rules use a richer syntax called sub-token rules. A sub-token rule does not describe which channels are available – it describes which subscription patterns the client is allowed to send in SUBSCRIBE(...).

This protects against clients sending overly broad patterns (like a.*.c.#) even when the intersection with real permissions would be non-empty.

Syntax

A sub-token rule is a dot-separated list of segments. Each segment is one of:

SegmentMeaning
storeExact literal. The subscription must have this exact segment.
?Any literal segment. The client can use any concrete value (de, fi, v2) but not *, #, >.
*Literal or wildcard. The client can use a concrete value or * at this position.
(a|b|prefix*)Alternatives. The subscription must have a literal matching at least one variant. Prefix variants like b* match any literal starting with b.
#Tail (0+). Only as the last segment. Allows any number of additional subscription segments (including zero).
>Tail (1+). Only as the last segment. Allows one or more additional subscription segments.

Matching rules

When a client sends SUBSCRIBE(pattern), the server checks each sub-token rule against the subscription pattern segment by segment:

Rule segmentSubscription segment must be
Literal "store"Exactly "store"
?Any literal (not *, #, >)
*Any literal or *
Alternatives (a|b|b*)A literal matching at least one variant
# (tail)0 or more valid subscription segments
> (tail)1 or more valid subscription segments

In the tail region (# or >), each remaining subscription segment can be a literal, *, or #/> (only as the last segment of the subscription).

Examples

Strict branch: store.sell.#

SubscriptionAllowed?Why
store.sellyestail is empty, # allows 0+
store.sell.statusyes1 tail segment
store.sell.*yes* is valid in tail
store.sell.#yes# at end of subscription
store.*noposition 2 must be literal sell
store.buy.statusnobuy != sell

Any-literal position: store.?.status.#

SubscriptionAllowed?Why
store.fi.statusyesfi is a literal, matches ?
store.de.status.v2yestail allowed
store.*.statusno? does not accept *

Wildcard position: store.*.status

SubscriptionAllowed?Why
store.*.statusyes* accepts *
store.fi.statusyes* accepts literal
store.fi.status.v2nono tail allowed

Alternatives: store.(sell|bay|b*).#

SubscriptionAllowed?Why
store.sellyesmatches sell
store.buy.status.v2yesmatches b*, tail allowed
store.bag.>yesmatches b*, tail allowed
store.pay.statusnopay not in alternatives

Tail # vs >:

  • a.b.# allows: a.b, a.b.c, a.b.c.d
  • a.b.> allows: a.b.c, a.b.c.d but not a.b

Invalid syntax (rejected at token creation)

  • store.sell|bay.status| outside parentheses (use store.(sell|bay).status)
  • store.(sell.status|buy).# – multi-segment inside alternatives (use two separate rules)
  • store.#.status# not at end
  • store..sell – empty segment

Limits

  • Maximum segments per rule: 32
  • Maximum segment length: 128 bytes
  • Maximum alternatives inside (...): 16

Allow vs deny

The base token model is allow-list: if a tenant/channel is not in the token, it is not allowed.

If your deployment supports explicit deny rules, keep them rare and use them only to carve out exceptions from a broad allow-root (example: allow orders.# but deny orders.internal.#). If deny is not supported, enforce exclusions by splitting roots (example: use separate roots pub.# vs sys.#).