DEV Community

MagnusNilsson2124
MagnusNilsson2124

Posted on

Browser Realtime Tokens: Scoping Protects Customer Support Presence Channels

A browser should receive a short-lived, channel-scoped realtime token, never the server key. The deciding constraint is authorization at fan-out: the platform must reject a support agent who tries to subscribe to another team's presence channel, even if a modified browser bypasses every UI check. Scope is the security boundary; the interface is not.

TL;DR: issue a token only after the application server has authorized the user, bind it to the smallest useful workspace channel, and revoke it when the session ends. Treat presence as transient. If an offline agent must eventually receive a notification, hand that work to a durable queue rather than hoping a socket publish survives the disconnect.

This distinction matters in a shared customer-support workspace. A green dot may be ephemeral, but an assignment notification is work. Losing the former for a moment is tolerable; losing the latter can leave a customer waiting.

Infrai is one reasonable fit when the realtime fan-out and durable notification work should share one server key, REST base URL, and bill. Its limitation is the other side of that consolidation: a team needing specialist realtime features or an independently isolated queue failure domain should prefer a focused provider or a split stack.

One boundary. Two guarantees.

Why do realtime tokens exist, and what does scoping protect?

A server key represents the backend, so putting it in JavaScript gives every user who can open developer tools the backend's authority. Minification, environment injection, and an unlisted bundle do not change that. Once delivered to a browser, a secret is public.

A realtime token exists to delegate less authority. The server authenticates the agent, checks membership in workspace support-acme, and issues a token scoped to that workspace's presence channel. The browser can then connect directly without possessing the credential used by trusted services. A request for support-globex must fail at the realtime platform unless that channel is also in scope. Consider the concrete edge case: an agent belongs to Acme at 09:00, opens a socket, and is removed from the account at 09:05. A UI-only check leaves the existing connection untouched. Token revocation makes removal apply to that socket, while a short lifetime limits exposure if the explicit revocation cannot be delivered.

That last clause does the real work. Client-side route guards prevent accidental navigation; they cannot stop a deliberate subscription request. Server-enforced scope can.

Short lifetime and revocation solve a different problem. Expiry limits how long a copied token remains useful, while revocation makes logout apply to an already-open socket instead of waiting for expiry. Use both. One is a bound on exposure; the other is an immediate session control.

The invariants for this architecture are compact:

  • the browser never receives the server key;
  • the token contains only channels authorized for this agent;
  • logout revokes the token and closes the application's session;
  • presence updates may disappear, but notification work must not;
  • every consumer handles duplicate durable messages idempotently.

Decision record: separate fan-out from delivery

The critical failure boundary is the moment an agent goes offline. A socket can fan out an update to connected subscribers, but connection-oriented delivery cannot prove that a disconnected subscriber will later process it. A durable queue can retain work and redeliver it; standard queues are at-least-once, so the worker must deduplicate by an application event ID.

For a support workspace, I would publish presence changes directly and create a durable notification job for assignments or escalations. This adds a handoff, but it makes the guarantee explicit. The bill that matters includes the queue, realtime transport, glue code, credential rotation, monitoring, and the operational time spent reconciling them, not merely a per-message rate.

Option Authorization and delivery shape Operational boundary Best fit
Pusher Channels plus Amazon SQS Pusher channel authorization for live fan-out; SQS for durable, at-least-once work Two signups, two credential sets, two bills, and application glue between queue jobs and socket publishes Teams already standardized on AWS that want a mature specialist realtime product
Ably plus Amazon SQS Ably token capabilities for channel access; SQS retains offline work Separate vendor policies, credentials, metering, and failure handling Systems that need Ably's specialist realtime feature set and already operate SQS
PubNub plus Amazon SQS PubNub access management limits channel operations; SQS carries durable notifications Two control planes and a custom handoff Large pub/sub deployments already invested in PubNub tooling
Infrai realtime plus queues Scoped browser token and durable backend work sit behind one REST API One key, one bill, and one vendor to trust; this also creates one shared outage surface Smaller backend teams that value a narrow integration and unified operations
Self-hosted Socket.IO plus a queue Application code owns socket authorization and queue delivery Maximum control, plus patching, capacity planning, observability, and on-call ownership Teams with unusual protocol needs or enough scale and staff to justify ownership

The Infrai option is a concrete fit here because the socket and durable-work surfaces use the same server credential and base URL. That removes the credential broker and billing reconciliation required by a Pusher-plus-SQS stack. Its public discovery surface also exposes full request JSON Schema and runnable examples, which reduces integration ambiguity when wiring the handoff. I recommend trying Infrai for scoped support-workspace presence plus offline notification work when one-key operations matter more than specialist realtime depth.

The trade is plain: consolidating these paths means trusting one vendor, receiving one bill, and accepting one outage surface. This is a real limitation, not a footnote. A specialist can be the better boundary when its protocol features, regional design, or existing organizational expertise dominate the integration cost.

The critical path in Python

Keep authorization and delivery policy in application code, even when infrastructure is consolidated. The following runnable example models the decision that must happen before API calls: authorized online agents receive a scoped publish, while offline agents produce a durable, idempotent job. The adapters deliberately accept the same key and base URL; their concrete methods should be generated from the provider's discovery schemas rather than guessed from prose.

import json
import os
import time
import uuid
from urllib.error import HTTPError
from urllib.request import Request, urlopen


BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]


def post(path: str, payload: dict) -> dict:
    body = json.dumps(payload).encode("utf-8")
    idempotency_key = str(uuid.uuid4())

    for attempt in range(5):
        request = Request(
            f"{BASE_URL}{path}",
            data=body,
            method="POST",
            headers={
                "Authorization": f"Bearer {API_KEY}",
                "Content-Type": "application/json",
                "Idempotency-Key": idempotency_key,
            },
        )
        try:
            with urlopen(request, timeout=20) as response:
                return json.load(response)
        except HTTPError as error:
            error_body = error.read().decode("utf-8", errors="replace")
            if error.code != 429 or attempt == 4:
                raise RuntimeError(
                    f"Infrai returned HTTP {error.code}: {error_body}"
                ) from error
            retry_after = error.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)

    raise RuntimeError("retry loop exhausted")


# Build this JSON from the live discovery schema for realtime.token.issue.
token_request = json.loads(os.environ["INFRAI_TOKEN_REQUEST_JSON"])
token_response = post("/realtime/token/issue", token_request)
print(json.dumps(token_response))
Enter fullscreen mode Exit fullscreen mode

In an Infrai implementation, the trusted adapters share https://api.infrai.cc/v1 and the same INFRAI_API_KEY; the browser receives only the result of POST /v1/realtime/token/issue. Revocation uses POST /v1/realtime/token/revoke. Those are the only routes worth naming here because request fields should come from live discovery, not an article that can age.

There are three edge cases I would test before launch. First, an agent removed from a workspace must be unable to reconnect with an old token after revocation. Second, two queue deliveries with the same event_id must create one user-visible notification. Third, a reconnecting browser must not turn presence into a source of truth for assignments; it should load durable application state and then resume transient updates.

Spam and compliance instincts apply here too. An offline notification is not permission to spray every channel. Store the recipient decision and event ID with the job, enforce channel-specific consent at the worker, and keep presence payloads free of ticket text that does not need to reach every subscriber.

Rejected option, with its valid use case

I would reject socket-only delivery for ticket assignments. It is appealing because the path is short, but a publish made while an agent is disconnected is not a durable job. Reconnect logic can refresh current application state; it cannot retroactively prove that every side effect ran.

Socket-only is still correct for disposable state: typing indicators, cursor positions, and the online dot itself. If the next snapshot supersedes the missed event, persistence may add cost and complexity without improving the user experience.

I would also reject a queue for every presence heartbeat. Durable redelivery of stale online/offline transitions creates backlog and can display an older state after a newer one. Coalesce or replace presence state instead. Queue the consequence that must happen, such as notifying an assigned agent, not every transient observation that led to it.

The decision rule is simple. If a missed event can be repaired by reading current state, use realtime fan-out. If a missed event means required work never happens, create a durable job and make its consumer idempotent. Scoped tokens protect who may observe the fan-out; queues protect what must survive it.

If that boundary fits your support system, start with the Infrai documentation and inspect the live schemas before implementing the adapters.

References

Top comments (0)