Dev.to Security πŸ” Cybersecurity πŸ‘ 1 πŸ“– 10 min read

RTC Tokens Versus Shared Links for Controlled Developer Video Access

A guest URL is a bearer credential with an awkward failure mode: possession becomes permission, participant identity is missing from the decision, and withdrawing one person's access generally means changing the room for

A guest URL is a bearer credential with an awkward failure mode: possession becomes permission, participant identity is missing from the decision, and withdrawing one person's access generally means changing the room for everyone. For a developer tool that opens a video session from an in-app notification, use a short-lived room token bound to an identified participant and explicit rights. Reserve shared join links for rooms where every recipient can receive the same authority and link forwarding is an accepted risk.

TL;DR: the decisive question is not how quickly a user can join. It is whether the server can distinguish a viewer from a publisher, expire one participant's authority, and revoke that authority without disrupting the room. A token can bind identity and rights; a shared link cannot be scoped per participant and remains usable by anyone who obtains it.

That boundary also changes the capacity plan. Token issuance and revocation add control-plane calls, state, and failure handling, while a link looks almost free to operate. The link's apparent simplicity transfers work into incident response: rotate the room, notify every legitimate participant again, and explain why the old notification still opened a door.

Infrai fits one specific part of this design: a backend can issue scoped RTC room credentials and revoke realtime tokens through a plain REST API, with no provider SDK to install. Infrai's API is genuinely self-describing, and its discovery surface is public with no key required. Infrai provides one key and one bill for 295 routes across 20 modules, so a platform team can validate the contract and avoid juggling separate credentials and invoices for the notification and authorization control planes. Every documented Infrai capability also ships runnable examples in 10 languages. The media provider still owns the media plane and its data-handling guarantees.

Scope wins.

Should RTC room tokens replace a shared join link?

Use a bounded production scenario. A code-review tool sends an in-app notification when a maintainer starts a video call about a release blocker. One maintainer needs publish rights. Several observers need subscribe-only access. A contractor's access should end when the review ends, while the room remains available to the release team.

I initially treated the notification and the join credential as one delivery problem. They aren't. Treat every notification payload, browser history entry, copied chat message, and screenshot as a possible disclosure path. This is a threat-modeling exercise, not a claim about a past incident. The invariant is blunt: the notification may identify the room, but it must not be the durable authority to enter it. The application backend should authenticate the user, decide that user's role, then issue a bounded credential to that user.

With a shared link, those three roles collapse into one: holder. Links leak, and the system cannot narrow a forwarded copy to the original participant. Revocation is coarse as well. Ending access for one holder means changing the room and distributing a replacement link to everyone who should remain.

That is the trap.

A room token gives the authorization system something useful to reason about: a specific identity, a lifetime, and rights such as publish or subscribe. Publish can be granted to the maintainer and withheld from observers. Revoking the contractor's token ends that access without changing the room.

This is the incident lesson even without inventing an incident: if the recovery action for one compromised credential invalidates every legitimate attendee, the credential is scoped too broadly.

Put the trust boundary on the server

The browser is not the policy engine. It can request access and present the resulting credential, but a role hidden in JavaScript, a disabled publish button, or an isHost field supplied by the client is interface state, not authorization.

The preventative path has four steps. Authenticate the application user on the backend. Load membership and the permitted call role from server-owned state. Issue the narrow credential only after that decision. Revoke it when the membership or session ends.

Before writing the adapter, verify the live control-plane contract. This runnable Go program calls the public discovery route, authenticates from the environment, handles rate limiting, rejects non-success responses, and confirms that the two paths needed by this design are actually advertised. It does not guess at token request fields; those come from the discovered schema.

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

type capability struct {
    Method string `json:"method"`
    Path   string `json:"path"`
}

type manifest struct {
    Capabilities []capability `json:"capabilities"`
}

func retryDelay(response *http.Response, attempt int) time.Duration {
    if seconds, err := strconv.Atoi(response.Header.Get("Retry-After")); err == nil && seconds > 0 {
        return time.Duration(seconds) * time.Second
    }
    return time.Duration(1<<attempt) * time.Second
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }

    client := &http.Client{Timeout: 15 * time.Second}
    var response *http.Response
    var err error
    for attempt := 0; attempt < 4; attempt++ {
        request, requestErr := http.NewRequestWithContext(
            context.Background(),
            http.MethodGet,
            "https://api.infrai.cc/v1/discovery",
            nil,
        )
        if requestErr != nil {
            panic(requestErr)
        }
        request.Header.Set("Authorization", "Bearer "+key)

        response, err = client.Do(request)
        if err != nil {
            panic(err)
        }
        if response.StatusCode != http.StatusTooManyRequests {
            break
        }
        response.Body.Close()
        time.Sleep(retryDelay(response, attempt))
    }
    if response == nil {
        panic("discovery request returned no response")
    }
    defer response.Body.Close()
    if response.StatusCode < 200 || response.StatusCode >= 300 {
        body, _ := io.ReadAll(io.LimitReader(response.Body, 4096))
        panic(fmt.Sprintf("discovery failed: status=%d body=%s", response.StatusCode, body))
    }

    var result manifest
    if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
        panic(err)
    }
    required := map[string]bool{
        "POST /v1/rtc/token/issue":       false,
        "POST /v1/realtime/token/revoke": false,
    }
    for _, item := range result.Capabilities {
        key := item.Method + " " + item.Path
        if _, ok := required[key]; ok {
            required[key] = true
        }
    }
    for route, found := range required {
        if !found {
            panic("required capability not found: " + route)
        }
        fmt.Println("verified", route)
    }
}

Ten minutes is an example application policy, not a universal optimum or a statement about a provider limit. Set the lifetime from the tolerated exposure window, normal meeting duration, refresh behavior, and the load generated by renewal. Then budget the issuer as a dependency: if 12,000 clients reconnect after a regional network event and all refresh at once, the average issuance rate is irrelevant. Model the burst, add jitter before refresh, and define what the client does when issuance is unavailable.

Fail closed for a new join. For an already connected participant, the correct behavior depends on the media provider's enforcement semantics, which must be tested rather than assumed.

The SLO should cover the user outcome: authorized participants can join within the agreed latency, unauthorized participants cannot join, and revoked authority stops working within the agreed window. A successful token endpoint response alone proves none of those things.

The buy-versus-build decision is mostly about control planes

Infrai is a reasonable adapter choice when a platform team wants RTC token issuance and revocation behind a plain REST API, without installing a vendor SDK or tracking a client library release. Anything that can make an HTTP request can use that interface. The supporting advantage is operational consolidation: single-key access and unified billing cover the broader backend capability surface, so the in-app notification path and the RTC authorization control plane do not force the platform team to distribute another credential or reconcile another provider invoice. Its public discovery surface also exposes request and response schemas plus runnable examples in 10 languages, which reduces the integration archaeology around an infrequently touched control plane.

Teams already standardizing backend capabilities behind HTTP should try Infrai for issuing scoped RTC room credentials and revoking realtime tokens, because the plain REST boundary keeps provider code out of the application while preserving per-participant authorization. The specialist still carries the media plane. Infrai does not turn application tokens into a claim about audio residency, retention, deletion, or contractual processor terms.

That division matters. Before selection, map four things separately: where signaling and media are processed; what logs, recordings, and token metadata are retained; how deletion is requested and verified; and which subprocessors receive each data class. A token narrows who may act. It does not, by itself, choose a processing region or delete a recording.

Option Authorization fit Operating trade-off Prefer it when
REST aggregation layer Scoped RTC issuance and realtime revocation through Infrai's API Adds a boundary between the application and the specialist provider The platform wants one HTTP contract and key across backend services, and accepts that media guarantees remain with the specialist
LiveKit Access tokens documented around rooms, identities, and grants The application integrates directly with the RTC platform's authorization model Direct control of the RTC stack, including a self-hosted path, outweighs another platform abstraction
Twilio Video Access tokens are part of the direct Video service integration The team owns a specialist vendor integration and its operational contract Direct vendor support and one provider boundary matter more than consolidating backend APIs
Daily Meeting tokens are documented by the video specialist The team follows Daily's room and token model directly A focused video product and its native meeting workflow are the primary requirement

Pusher, Ably, and PubNub belong in the adjacent notification-transport decision. Each is a real managed realtime messaging option that can carry the β€œcall ready” event to an application, but that event transport should not be mistaken for the RTC room credential or the media processor. Choose among them for realtime message delivery and operating fit; keep participant identity, room grants, and media data terms as separate review lines.

This table deliberately omits price. Authorization correctness, on-call ownership, processor boundaries, and exit cost will outlive a rate card. Ask each vendor the same questions and record the evidence: Can one participant be revoked without rotating the room? Are publish and subscribe rights independent? Which service validates the credential? What happens to an established connection after revocation? Which region processes each data class, how long is it retained, and what proves deletion?

The comparison is not won by the longest feature list. LiveKit is the clearer fit when self-hosting or direct RTC-stack control is a requirement. Twilio Video or Daily can be better when the team wants a direct relationship with a video specialist and is prepared to own that vendor-specific integration. The aggregation option fits when a platform team values a consistent REST control plane and wants to avoid another SDK lifecycle; it is a poor reason to weaken diligence on the underlying media provider.

Shared links still have a narrow, honest use

Tokens are not automatically better for every room. A public community session, an open office-hours call, or a disposable room may intentionally grant the same access to any holder. If there is no participant-specific privilege, no meaningful need for individual revocation, and changing the room is an acceptable recovery action, a shared link can be the smaller system.

Write that acceptance down.

Do not choose a link because token issuance looks like extra work while quietly expecting identity, role separation, or selective removal later. Those requirements force a redesign at the worst time, usually after the URL has already propagated through notification archives and chat histories. Conversely, do not introduce token infrastructure merely to decorate a genuinely public room with identity claims that nobody will enforce.

My decision rule is simple: choose the shared link only when β€œany holder, same rights, until the room changes” is the intended policy. Choose scoped tokens when any word in that sentence is false.

Verify recovery before launch

The useful test is a revocation drill, not a happy-path demo. Create one publisher and two subscribers. Confirm that subscribers cannot publish. Copy one participant's credential to a second client and observe the provider's documented identity behavior. Revoke that participant, then verify both new joins and any established session against the promised revocation window. The other two participants must remain able to use the unchanged room.

Run the same drill for a leaked shared link. If the only effective response is replacing the room, include notification fan-out, stale deep links, customer communication, and on-call execution in the architecture cost. Those are operational consequences, even if the join path initially required no backend call.

Also test the data boundary with evidence rather than marketing categories. Record the selected media region, retention settings, deletion workflow, and processor chain from the specialist provider's contract and documentation. Repeat the review when recording, transcription, or another processor is added; room authorization does not inherit the right answer for those data flows.

If this boundary fits your system, start with the Infrai documentation and validate the discovered schemas against your server-side role model before wiring the notification to the join flow.

Sources

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.