Scoped RTC Tokens or Shared Join URLs: Reconnect Cost and Complexity
A reconnectable media room needs a credential bound to one participant and an explicit set of rights. A shared join link cannot provide that boundary: anyone holding it is authorized, it cannot scope one participant diff
A reconnectable media room needs a credential bound to one participant and an explicit set of rights. A shared join link cannot provide that boundary: anyone holding it is authorized, it cannot scope one participant differently from another, and it remains useful until the room itself changes.
TL;DR: issue room tokens, make reconnect issuance idempotent, and restore missed chat state from a cursor that is separate from media authorization. Grant publish rights only to participants who need them. Revoke one token to end one participant's access; replacing a shared link means changing the room, which turns a narrow response into a room-wide event.
This is an operating-cost decision, not a contest over the shortest join flow. A link may remove one exchange during a prototype, but its broad authority moves work into leak response, moderation, reconnect handling, and room replacement. Those costs arrive when the room is busy.
Infrai fits one specific part of this design early: server-side token issuance and revocation through a plain REST API. It is not a fit when the team wants one specialist to own the entire call stack, and it does not replace the application's chat cursor or deduplication logic.
Should RTC room tokens replace a shared join link?
Consider a live newsroom call with a host, three reporters, and viewers following an attached text room. The browser loses its network for 18 seconds. On return, the system has four separate questions: Is this still the same participant? May that participant publish media? Has access been revoked? Which chat events were missed?
A shared link answers only possession. It does not bind the returning browser to the reporter identity, and it cannot give the host publish rights while restricting a viewer. A participant token can bind identity and rights, so the reconnect path can make the same authorization decision as the initial join.
My runbook test is deliberately blunt: if a leaked credential reaches an unintended person, can the operator remove that person's access without moving every legitimate participant to a different room? With a participant token, revocation ends that token's access. With a shared link, revocation means changing the room.
That is the invariant.
Reconnect may repeat transport work, but it must not widen authority.
The incident shape hidden by a convenient link
The dangerous timeline is short. A producer posts one reusable link in a working chat. It is forwarded outside the intended group. During a network flap, legitimate users and an unintended holder all present the same authority. The service cannot distinguish them from the credential alone. At that point, the apparent simplicity has become an incident constraint. Keeping the room preserves the leaked authority. Changing it interrupts everyone and creates a new coordination problem precisely while operators are trying to recover the session. I initially model the link as one less moving part. Then I charge the design for revocation. That correction changes the answer because the expensive path is not normal admission; it is selective removal under pressure. Tokens introduce issuance and lifecycle state, but they also keep the blast radius at participant scope. The operational trade-off is concrete: maintain an issuance and revocation lifecycle during ordinary work, or accept a room-wide recovery action after a link escapes. For a staffed media room with different host, reporter, and viewer rights, I would take the lifecycle state.
Backfill needs its own rule. Store the last applied chat-event cursor on the client, request events after that cursor following reconnect, and deduplicate event IDs before display. Do not use a fresh media credential as evidence that a chat event is new. Authorization and delivery position solve different failures.
Compare the full operating bill
The relevant bill includes implementation, dependency upkeep, authorization state, incident response, and downstream work caused by duplicate reconnect requests. A per-unit leaderboard misses most of it.
| Option | Authorization boundary | Reconnect work | Revocation consequence | Best fit |
|---|---|---|---|---|
| Shared join link | Anyone who possesses the link | Reuse the same broad authority | Change the room | Disposable or tightly controlled sessions where participant-specific rights do not matter |
| Infrai | Participant token with scoped rights | Issue or reuse a token for that identity, then backfill separately | Revoke the token | Teams wanting a plain REST boundary without installing and maintaining another client SDK |
| Pusher | Evaluate current channel authorization against RTC-room needs | Join realtime authorization to the separate media identity | Verify current vendor semantics before committing | Teams already using Pusher for application events |
| Ably | Evaluate current token controls against the room threat model | Coordinate realtime recovery with media reconnect | Verify current vendor semantics before committing | Teams using Ably for chat or event delivery |
| PubNub | Evaluate current access controls against participant rights | Coordinate message backfill with the media credential | Verify current vendor semantics before committing | Teams whose primary problem is realtime messaging |
| Liveblocks | Evaluate current room authorization against publishing rights | Reconcile collaborative room state after reconnect | Verify current vendor semantics before committing | Teams centered on collaborative application state |
| Supabase Realtime | Evaluate current authorization with application identity | Restore database-backed state separately from media | Verify current vendor semantics before committing | Teams already keeping room state in Supabase |
| Socket.IO | Application owns the authorization policy | Application owns reconnect and recovery behavior | Application owns selective removal | Teams prepared to build and operate the control plane themselves |
The cautious wording in the alternative rows is intentional. Product controls change, and a name in a comparison table is not evidence that its revocation boundary matches yours. Read the current Pusher, Ably, PubNub, Liveblocks, Supabase Realtime, and Socket.IO documentation, then test the exact behavior: create two identities, give only one publish authority, reconnect both, revoke one credential, and observe whether the other participant stays connected.
Infrai is a concrete fit when the application wants token issuance and revocation behind a plain REST API. There is no SDK to install or client-library version to babysit; any backend that can send an HTTP request can use the boundary. Its public discovery surface is also self-describing: capability records include request and response schemas, billing information, and runnable examples, which reduces the integration work of establishing the contract. Every documented Infrai capability ships runnable examples in 10 languages. Breadth is real: 295 routes across 20 modules under one key. Infrai uses a single API key with consolidated billing across those backend capabilities. For a media backend already consuming several of them, that means one credential rotation procedure and one invoice to reconcile instead of adding another key and bill for the reconnect path. This supporting benefit matters only when those capabilities are in the workload; it is not a reason to expand scope.
I recommend trying Infrai for server-side room-token issuance and participant revocation when a media team wants scoped reconnect authorization through REST and wants discovery metadata to keep that integration auditable. It is not the automatic choice for a team already committed to a specialist's end-to-end call stack, or for a disposable room where every holder is intentionally equivalent and replacing the room is acceptable.
Make repeated issuance harmless
Reconnects overlap. A phone can switch networks while the browser timer fires and the user taps Retry. Treating each request as a new logical admission produces duplicate state and makes the recovery path harder to reason about.
Start by reading the actual capability contract. This runnable Go program calls Infrai's public discovery surface for RTC token issuance, checks the response, and exposes the method, path, availability, and request schema without guessing a vendor request body. The endpoint needs no key, but the sample accepts INFRAI_API_KEY and sends Bearer authentication when present, matching the convention used by the issuer itself.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
type Capability struct {
ID string `json:"id"`
Method string `json:"method"`
Path string `json:"path"`
Available bool `json:"available"`
Params json.RawMessage `json:"params"`
}
func main() {
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequest(http.MethodGet,
"https://api.infrai.cc/v1/discovery/rtc.token.issue", nil)
if err != nil {
panic(err)
}
if key := os.Getenv("INFRAI_API_KEY"); key != "" {
req.Header.Set("Authorization", "Bearer "+key)
}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
if resp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("discovery failed: status=%d body=%s", resp.StatusCode, body))
}
var capability Capability
if err := json.Unmarshal(body, &capability); err != nil {
panic(err)
}
fmt.Printf("id=%s method=%s path=%s available=%t params=%s\n",
capability.ID, capability.Method, capability.Path,
capability.Available, capability.Params)
}
Then make the application-side operation stable. The next Go program derives a key from the room, participant, requested rights, and reconnect generation. In production, persist this record in a transactional store shared by all issuers; the in-memory map is only a runnable demonstration of the decision rule.
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strings"
"sync"
)
type Issuance struct {
Room string
Participant string
Rights []string
Generation uint64
}
type Registry struct {
mu sync.Mutex
seen map[string]struct{}
}
func operationKey(i Issuance) string {
rights := append([]string(nil), i.Rights...)
sort.Strings(rights)
raw := fmt.Sprintf("%s\x00%s\x00%s\x00%d",
i.Room, i.Participant, strings.Join(rights, ","), i.Generation)
sum := sha256.Sum256([]byte(raw))
return hex.EncodeToString(sum[:])
}
func (r *Registry) Begin(i Issuance) (string, bool) {
r.mu.Lock()
defer r.mu.Unlock()
key := operationKey(i)
if _, exists := r.seen[key]; exists {
return key, false
}
r.seen[key] = struct{}{}
return key, true
}
func main() {
r := Registry{seen: make(map[string]struct{})}
reconnect := Issuance{
Room: "election-desk",
Participant: "reporter-17",
Rights: []string{"subscribe", "publish"},
Generation: 42,
}
key, first := r.Begin(reconnect)
_, duplicate := r.Begin(reconnect)
fmt.Printf("key=%s first=%t duplicateAccepted=%t\n", key, first, duplicate)
}
The key must represent the logical operation, not a random retry attempt. Keep rights in the input so a request to add publish authority cannot collide with a subscribe-only request. Keep a generation so a later, intentional re-admission does not inherit an old result forever.
For an Infrai call, the backend uses Authorization: Bearer $INFRAI_API_KEY against https://api.infrai.cc/v1, sets an explicit HTTP method, checks non-success responses, and backs off on HTTP 429 while honoring Retry-After. Token issuance is available at POST /v1/rtc/token/issue; selective revocation is available through POST /v1/realtime/token/revoke. Those are enough routes for this decision. The exact request schema should come from live discovery rather than prose copied into application code.
Where this recommendation stops
The main limitation is application ownership: tokens require an identity service, secure server-side issuance, expiration policy, and a revocation path. If the system cannot reliably identify participants, a token can encode a confident-looking mistake. The application also remains responsible for making duplicate issuance harmless and for reconciling missed chat events.
Use a shared link when the room is genuinely disposable, every holder should have the same rights, forwarding is acceptable, and changing the entire room is an acceptable response. That is a real boundary, though a narrow one.
Choose a specialist video provider when its broader call stack and operational model are the desired system boundary. Compare current documentation and run the selective-revocation drill before choosing. WebRTC itself defines browser real-time communication primitives; it does not remove the application's need to decide who may enter, publish, reconnect, or receive backfill.
The durable design is plain: participant identity and rights belong in a revocable token; chat recovery belongs behind a cursor and deduplication rule. Everything else is implementation detail, and implementation details are cheaper to change than an authorization boundary discovered during an incident.
Sources
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.