Skip to main content
The iii-worker-manager worker owns the connections between workers and the engine. It always runs a trusted listener (on 49134 by default) that your own workers connect to through III_URL. To let an untrusted client connect, you add an RBAC-gated listener: a second port where every connection is authenticated by a function you write and every call is checked against an allowlist.
iii-worker-manager is always running, so there is nothing to add; you configure it directly in config.yaml. This page is a quick tour of RBAC listeners. For the full configuration see the iii-worker-manager worker docs.

Trusted and RBAC listeners

The default 49134 listener is trusted: any worker that reaches it can register functions and call anything. That is fine for workers you run yourself, but not for a browser or other untrusted worker. An RBAC listener runs on its own port, gates every connection through an auth function, and restricts each session to an allowlist of functions.
Declaring any iii-worker-manager in config.yaml replaces the default 49134 trusted listener. You must also declare the trusted 49134 listener yourself alongside the RBAC one, otherwise your own workers lose the port they connect through.
Declare one iii-worker-manager entry for each listener:
config.yaml
The stream::* rule above exposes deprecated functions. stream::* is deprecated (iii-stream) and will be removed in an upcoming release. Behavior is unchanged for now. Expose your own domain functions instead; see Migrate from iii-stream and pubsub.
A browser client connects to the RBAC port with iii-browser-sdk; see Ch. 7: Bring in the browser. auth_function_id names a function the worker invokes once per connection to authorize the worker to connect (or reject it if authorization fails). That is covered in the next section.
auth_function_id resolves in the default namespace. Register the auth worker there. An auth worker in another namespace makes the listener reject every connection.
expose_functions is the allowlist of function IDs a session may call. Each entry is either a glob match("...") on the function ID or a metadata: selector that matches any function registered with that metadata:

Namespace-scoped function rules

An expose_functions rule without a namespace applies to default only. Add namespace when the session must call a function in another namespace:
The match value supports * wildcards for the function id. The namespace value is an exact match and does not support wildcards. Add one rule for each namespace that the session can access. A function’s metadata is an arbitrary JSON object set when the function is registered, so you can shape it however your access model needs and gate on exactly the fields you choose, for example { public: true }, { tier: "premium" }, or { scopes: ["read"] }.
All access is gated system-side. The access of an untrusted worker must not come from that worker. For example, metadata.public = true above is defined and managed by the trusted iii-worker-manager and is not part of a payload sent by the worker.
auth_function_id is connection authorization, and it is not deprecated. The deprecated auth_function setting belongs to iii-stream, which is a separate worker.

Write the auth function

The auth function runs during the WebSocket upgrade, before the client is connected. It receives the request’s headers, query parameters, and client IP, and returns the session’s permissions. Throw to reject the connection.
The auth function can grant multiple authorizations so a single function can be used to authorize multiple types of workers. In practice this is a balance between authorizing similar workers and separating dissimilar workers to separate iii-worker-manager definitions. You can define any number of iii-worker-managers in iii’s configuration.
src/index.ts
The auth worker itself connects to the trusted 49134 port; it is one of your own workers. Only the untrusted client connects through 3110. The returned object is an AuthResult:

Scope a session to namespaces

namespaces maps a namespace to the function IDs the session may call inside it. An entry accepts an exact ID or a wildcard, in either the bare or the match("...") spelling expose_functions uses:
The keys carry a second meaning: they are the namespaces the session may declare on engine::workers::register and address on a call. A session holding this result registers in browser or linkly; declaring default, or any other namespace, is refused with FORBIDDEN, and a call into a namespace outside the list is refused with FORBIDDEN. An entry with an empty list, like linkly above, grants the namespace and no functions inside it: the session registers there and reaches only what expose_functions exposes. Return an empty namespaces (or omit it) to leave the session unscoped: it declares any namespace, and only allowed_functions and expose_functions apply.

Intercept calls with middleware

Set middleware_function_id on the listener to run a function on every invocation that comes through the RBAC port. It receives the call plus the session context from the auth result, and acts as a proxy: forward the (optionally rewritten) call and return its result, or throw to reject.
config.yaml
src/index.ts
The rbac block also accepts an on_function_registration_function_id hook. It runs when an untrusted session registers a function, and can remap the registration (return the mapped fields) or throw to deny it. Its input includes the function’s args_format, private and observable options, and its answer may rewrite them. on_observer_registration_function_id does the same for observers. It receives { observer_id, function_id, namespace, args, filters, target_function_id, target_namespace, metadata, context }. An object answer may rewrite function_id, target_function_id, args, filters, and metadata; any other answer, or an error, denies the observer with FORBIDDEN. Independently of the hook, a session may observe a function only when its grants (expose_functions, namespaces) cover both the observed function and the target. A worker that reconnects and reattaches keeps its observers only as far as its new session allows: the engine checks the grants again and calls the hook again for each observer, with the registration as the worker asked for it and the new session’s context. An observer either check refuses is disposed with FORBIDDEN. An object answer keeps the observer, with the fields it names rewritten and the others left as the worker asked for them. Any other answer, or an error, disposes the observer with FORBIDDEN. A private function accepts calls only from the connection that hosts it. A call to it from any other session fails with FORBIDDEN, whatever the session’s grants, and an observe that names it as the target fails with FORBIDDEN. A session whose grants cover a private function may observe it. Calls a worker makes to its own functions normally run in-process (local dispatch). For a session with a middleware_function_id, the engine turns local dispatch off, so every call still passes through the middleware, except calls of a function the session hosts privately: those skip the middleware and still run in-process, because the middleware’s forward would come from another connection, which a private function refuses.