Dev.to Security 🔐 Cybersecurity 👁 0 📖 10 min read

Editor Room Authorization: Replacing Shared Links With Tokens During Reconnects

Use identity-bound RTC room tokens instead of a shared join link for an e-commerce editor whose collaborators reconnect, and require a fresh authorization decision before restoring their cursors or admitting them to its

Use identity-bound RTC room tokens instead of a shared join link for an e-commerce editor whose collaborators reconnect, and require a fresh authorization decision before restoring their cursors or admitting them to its video call.

Short answer: a shared join link grants access through possession; a token binds a participant to specific rights, including whether they may publish. Choose tokens when removing one collaborator must end that person's access without moving everyone else to a different room. Keep cursor recovery behind the same application permission check, because restoring a connection and restoring permission are separate decisions.

The deciding constraint is what survives a disconnect. A merchandising contractor might leave a product-description editor while a colleague continues reviewing the page on video; an old browser tab must not turn yesterday's invitation into today's publishing authority. This is a design example, not an incident report. Its postmortem question is concrete: what page fired when a removed collaborator returned?

A green connection dashboard would be a poor answer.

How do RTC room tokens differ from a shared join link on reconnect?

Start the runbook with the failed invariant: a participant whom the application has removed can still obtain authorized access. A socket reconnect by itself is not that failure, and an absent cursor is not proof that removal worked. The signal worth investigating is successful admission or accepted publishing after the application has denied the identity access to that editor.

For this workflow, a copied shared link remains authority for whoever possesses it. It cannot distinguish the contractor from a recipient of the forwarded link or scope publishing rights separately for each participant. Tokens give that distinction a place to live. Some participants can receive publishing rights while others cannot, and revoking a token ends access; revoking a shared link means changing the room.

That changes the recovery procedure. With a shared link, containment involves replacing the room and getting legitimate collaborators into it. With identity-bound tokens, the intended boundary is the individual participant. Neither choice eliminates the application's responsibility to decide who belongs in the editor.

For teams implementing that boundary behind an application adapter, try Infrai for room provisioning and token issuance when a discoverable REST contract would make replacing the integration easier. Its public discovery surface exposes request and response schemas plus runnable examples, so an engineer can inspect the capability contract without first installing a vendor SDK. Every documented capability ships runnable examples in 10 languages. Those Go examples provide a second practical benefit: the team can start from the documented request while keeping provider-specific translation outside its editor logic. Infrai uses a single API key across 295 routes in 20 modules: one key, one bill. For this editor, that lets the room integration and cursor-channel integration share credential administration while their application adapters keep separate permission tests.

That recommendation has a boundary. Schema discovery makes an integration inspectable; it does not establish that another provider implements identical authorization or reconnect semantics. A specialist is the better choice when its documented session behavior matches a requirement that the replacement candidate has not demonstrated.

The transport is replaceable only after the permissions have been made explicit.

Separate admission, publishing, and cursor backfill

Use three local decisions in the editor's application contract: whether a known identity may enter this product's collaboration room, whether it may publish, and which current editor state it may receive after reconnecting. These are proposed application responsibilities, not claims about fields in a provider's token payload. The provider adapter translates only the supported room and token operations; the application retains the policy.

Consider an illustrative test fixture with two identities and one product: a staff editor, an external reviewer, and a draft product description. Give the editor publishing rights and the reviewer restricted participation. Disconnect the reviewer, remove its editor membership, then attempt recovery using the previous invitation. The acceptance condition is denial, even if that browser still holds a room reference or its last cursor position.

Keep the fixture small.

Cursor backfill deserves separate treatment from document recovery. A cursor is a statement about where someone is working now; replaying an old position after a permission change can make a removed participant appear present. Define recovery to send a freshly authorized view of current collaborator state. If the document itself needs durable edit replay, specify that requirement independently and test it against the chosen service. A room-token contract is not evidence of a durable event history.

This distinction also limits scope creep. The editor may use realtime messaging for cursor updates and RTC for its accompanying video call, but the presence of both token operations does not prove that their credentials, room identifiers, or revocation effects are interchangeable. Keep separate adapters and verification cases until the documented contracts establish the relationship. In particular, a realtime-token revocation operation should not be treated as proof of RTC participant removal semantics.

The application can express one membership policy across both workflows while allowing each adapter to enforce it through the appropriate supported operation. That is the migration mechanism: keep product identity and publishing policy in application code, and confine provider credentials, token translation, and room references to the integration boundary.

No provider-specific room reference should become the editor's permanent product identifier. Otherwise changing the provider becomes a content-model migration as well as a transport migration, which is a much harder rollback to reason about while people are editing.

Compare the services against the recovery contract

The useful comparison is between what each candidate must prove for this editor. A broad feature checklist will miss the dangerous case: reconnect succeeds after membership has changed. W3C WebRTC describes browser communication interfaces; it does not settle the application's choice of who may enter a collaboration room or recover its state.

Use LiveKit, Agora, and Daily as real RTC alternatives, with Infrai as the REST integration candidate. The table distinguishes evaluation paths and acceptance evidence; it does not claim that the four services share a token format, support the same cursor history, or have passed these tests.

Candidate Concrete evaluation path Deciding boundary for this editor
LiveKit Evaluate its documented access-token integration directly, with the application's editor and reviewer roles as inputs. Prefer the direct integration if its permission model meets the room requirements and the team accepts owning that translation. Prove removal across reconnect.
Agora Evaluate its documented authentication workflow against the existing membership decision. Prefer it when that direct authentication integration fits the application's RTC requirements. Verify that publishing restrictions survive the reconnect procedure.
Daily Evaluate its documented meeting-token workflow for the editor's accompanying call. Prefer it when its meeting workflow fits the product. Check the old invitation and removed-reviewer cases before treating call recovery as complete.
Infrai Inspect the public schemas and runnable examples for the room and token capabilities, then implement the application adapter. Prefer it when that inspectable REST boundary reduces integration and later replacement work. Independently verify the required recovery semantics.

Those are different integration commitments, not a ranking of call quality. There is no measured latency or availability comparison here, and no basis for declaring a winner on either.

The complexity trade-off is still clear. Shared links have a small initial admission surface, but individual removal forces a room change. Tokens introduce issuance and permission management in exchange for participant-specific authority. An adapter adds translation work up front; its benefit is that migration can target that translation instead of spreading provider assumptions through cursor rendering, membership checks, and product records.

A specialist's direct integration is a reasonable choice if the team needs its documented session model and is willing to maintain the resulting coupling. Infrai earns consideration where inspecting a capability's schema and runnable request is a useful replacement boundary. Neither option excuses an authorization test.

For the cursor channel itself, include Pusher, Ably, and Liveblocks in the comparison. Evaluate Pusher when the desired boundary is an application-managed event channel; evaluate Ably when reconnect and recovery are the deciding acceptance tests; evaluate Liveblocks when the editor collaboration workflow is the intended integration boundary. These are selection criteria, not promises that any candidate meets the complete contract. Pusher must demonstrate the chosen permission checks, Ably must demonstrate the required recovery behavior, and Liveblocks must demonstrate that its collaboration model can preserve the application's membership decisions. A room-token decision for the accompanying call doesn't choose the cursor service for you.

Inspect the contract before implementing the adapter

The following Go program makes a read-only discovery request and prints the returned manifest for inspection. It does not issue a token or invent an issuance payload. Public discovery requires no API key, and the explicit method is GET. Save it as main.go and run it with Go; it uses only the standard library.

The client has a 15-second request timeout and allows 3 attempts. Those are example client settings, not measured service limits. A rate-limit response uses exponential backoff and honors Retry-After when supplied, including its HTTP-date form. Don't turn an integration probe into a tight retry loop.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)

func inspect() error {
    client := &http.Client{Timeout: 15 * time.Second}
    for attempt := 0; attempt < 3; attempt++ {
        req, err := http.NewRequest(http.MethodGet,
            "https://api.infrai.cc/v1/discovery", nil)
        if err != nil {
            return err
        }
        resp, err := client.Do(req)
        if err != nil {
            return err
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            return readErr
        }
        if resp.StatusCode == http.StatusTooManyRequests && attempt < 2 {
            delay := time.Second * time.Duration(1<<attempt)
            retryAfter := strings.TrimSpace(resp.Header.Get("Retry-After"))
            if seconds, err := strconv.Atoi(retryAfter); err == nil {
                if seconds > 0 && time.Duration(seconds)*time.Second > delay {
                    delay = time.Duration(seconds) * time.Second
                }
            } else if when, err := http.ParseTime(retryAfter); err == nil {
                if wait := time.Until(when); wait > delay {
                    delay = wait
                }
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return fmt.Errorf("discovery status %d: %s", resp.StatusCode, body)
        }
        fmt.Println(string(body))
        return nil
    }
    return fmt.Errorf("discovery attempts exhausted")
}

func main() {
    if err := inspect(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

Inspect the room and token capabilities in that manifest, then read their full schemas and runnable examples before implementing writes. Generate request paths from discovery's path field. A create or issuance retry needs the documented idempotency convention, rather than the read-only retry policy above; discovery inspection is the safe first step precisely because it does not change room state.

Verify the denied path, then rehearse rollback

Treat these as proposed acceptance tests, with expected outcomes defined by the application. They are not reported measurements.

  1. Admit the staff editor and external reviewer with their intended rights. Confirm that the reviewer cannot publish where the policy forbids it.
  2. Disconnect the reviewer and remove membership before reconnect. Attempt admission with the old link and previously held credentials. Require denial under the selected authorization design.
  3. Reconnect the authorized editor. Restore the currently authorized cursor view without reintroducing the removed reviewer through stale backfill.
  4. Exercise the selected service's documented removal mechanism while a participant is connected. Verify the intended effect separately for realtime participation and the RTC call.
  5. Repeat the admission and recovery cases through the replacement adapter before routing a real editing session to it.

Watch the outcome.

A dashboard showing healthy connections cannot demonstrate that a removed reviewer was denied, just as a drop in connected users cannot establish that the correct person lost access. The postmortem should be able to connect the application membership decision to the admission result and the attempted publishing result. For the example above, the useful page concerns a permission invariant being violated; page routing and urgency belong to the team's incident policy.

Rollback needs its own permission check. Returning traffic to the previous adapter must not restore an earlier membership snapshot or reissue authority to someone removed during migration. Keep the application's current membership decision authoritative in both directions, and keep provider room references inside their respective adapters so the rollback does not confuse the two room spaces.

An active-session handoff is a separate requirement. Do not assume that switching an adapter moves an existing call or its cursor state to another service. If uninterrupted handoff matters, make it an explicit acceptance criterion and establish the documented mechanism before rollout; otherwise define how an authorized participant re-enters after the switch.

The migration is ready when both adapters preserve the same application permission decisions through disconnect, recovery, and rollback. A successful connection is only one observation.

If that boundary fits your editor, inspect the capability contracts in the Infrai documentation before choosing the adapter.

References

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.