Header Logo

Building a Locked-Down Interactive Signage Avatar: Auth Flows for FastAPI, React, and LiveKit

Building a Locked-Down Interactive Signage Avatar: Auth Flows for FastAPI, React, and LiveKit

FastAPI, React, and LiveKit auth patterns for locked-down signage avatars: short-lived browser sessions, server-side keys, and embeds.

Introduction


Locked-down signage systems look simple on the surface: a screen in a kiosk, a browser in fullscreen, maybe a camera and microphone if the interaction is live. The tricky part is the auth model. You want the device to render an interactive avatar, connect to realtime media, and maybe receive short-lived instructions or session state, without ever turning the browser into a bearer-token dumpster fire.


This post walks through a practical way to do that with a FastAPI backend, a React frontend, and a LiveKit-based realtime avatar stack. By the end, you should have a clean pattern for:


  • issuing short-lived browser session tokens from FastAPI

  • keeping API keys server-side only

  • attaching a realtime avatar to a LiveKit voice agent

  • rendering an interactive avatar in React without exposing privileged credentials


I’ll also point out where Protoface fits in, since it provides the avatar surface without forcing you to invent your own video-face pipeline.


Start with the trust boundaries


For signage, the browser is not a trusted environment. If someone can inspect the DOM, they can usually copy anything you put there: access tokens, API keys, voice presets, custom instructions, session IDs, websocket URLs, all of it. So the core rule is:


Only mint privileged credentials on the server, and only mint short-lived, narrowly scoped credentials for the browser.


That means your FastAPI app should own the secret material:


  • Protoface API keys

  • LiveKit server credentials, if you are creating rooms or access tokens yourself

  • any per-device policy decisions, like allowed origins, rate limits, or instruction templates


The browser should get a session token or embed URL that is useless outside the specific origin, time window, and interaction you intended. If the kiosk is fully locked down, you can also pair this with origin checks, tenant-scoped session IDs, and server-side rate limits.


Recommended flow for a signage client


A good production flow looks like this:


  1. The React app loads from a known origin, for example https://signage.example.com.

  2. The app calls a FastAPI endpoint like /api/signage/session.

  3. FastAPI authenticates the kiosk or the operator, then creates a short-lived avatar/session authorization object server-side.

  4. The browser receives only what it needs to connect: a session token, room name, or signed iframe/embed URL.

  5. The client joins the realtime channel and renders the avatar.


The key design choice is whether the browser talks directly to your realtime service or whether it loads a fully managed iframe. For a locked-down signage deployment, the iframe path is often the simplest because it removes almost all client-side auth complexity. If you need deeper UI integration, you can still use a custom React client and keep the secrets server-side.


FastAPI: mint a short-lived session, not a permanent credential


FastAPI should act as the policy and token minting layer. You can store your Protoface API key in an environment variable and use it only from server code. A minimal endpoint might validate the requester, choose the voice and instructions, and create a new realtime session through the REST API or Python SDK.


from fastapi import FastAPI, HTTPException

}
from fastapi import FastAPI, HTTPException

}
from fastapi import FastAPI, HTTPException

}


A few implementation notes matter here:


  • Keep the token lifetime short. For signage, minutes are usually enough.

  • Bind session creation to origin, tenant, device ID, or a signed login cookie if possible.

  • Never return your server API key to the browser, even temporarily.

  • Make session creation idempotent if your client may retry aggressively after network flaps.


If you prefer not to call the REST API directly, the Python SDK is a better fit for structured server code. The exact method names depend on the SDK version, but the pattern is the same: create a client with the server key, create an avatar session, and return only the browser-safe fields.


React: keep the client dumb and ephemeral


The React side should do as little as possible. Its job is to fetch the session from FastAPI, connect to the realtime layer, and render the avatar. If you use a custom client rather than a managed iframe, keep all session negotiation on the server and all secret-bearing decisions out of the bundle.


import { useEffect, useState } from "react";

}
import { useEffect, useState } from "react";

}
import { useEffect, useState } from "react";

}


In a real app, that client would establish the LiveKit connection, subscribe to the relevant tracks, and render the avatar video element. The important part is that the browser never sees a server API key and never learns more than the minimum needed for the current session.


LiveKit: attach the avatar to the voice agent


If your interactive signage uses a voice agent, the avatar should be attached at the agent layer so lip sync and speech timing stay aligned. This is where the LiveKit plugin surface is useful: it drops the avatar into your agent pipeline so the voice output and talking face stay synchronized.


In practical terms, the agent still handles ASR, LLM, and TTS. The avatar consumes the audio-driven timing and produces the rendered face stream. That avoids a common failure mode where the voice path and the visual path are managed separately and drift over time.


# illustrative example; exact setup depends on your agent stack

cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
# illustrative example; exact setup depends on your agent stack

cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
# illustrative example; exact setup depends on your agent stack

cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))


Two operational gotchas are worth calling out:


  • Latency matters. If the avatar is too far behind TTS, the illusion breaks. Keep your media path tight and avoid unnecessary proxy hops.

  • Room lifecycle matters. Signage clients reconnect more often than you think. Make sure the avatar session can survive transient disconnects or be recreated cleanly.


If your deployment already uses LiveKit for audio transport, this keeps the topology simple: the browser joins a room, the agent publishes audio, and the avatar is just another synchronized media surface in the same call graph.


One clean way to solve the browser auth problem


For locked-down signage, the managed iframe approach is often the best trade-off. Instead of shipping realtime auth logic into React, your backend creates a per-embed session and returns an iframe URL. The browser never gets an API key, and you can enforce parent-origin allowlists, per-embed instructions, voice selection, and rate limits at the platform boundary.


That simplifies the client significantly:


<iframe
></iframe>
<iframe
></iframe>
<iframe
></iframe>


The security benefit is real: the parent app can still be complex, but the embedded avatar runs with its own short-lived authorization model. If the kiosk is compromised, the blast radius is smaller because there is no long-lived browser-held secret to exfiltrate. For developers who want to skip the mechanics and focus on behavior, the docs cover the embed model and the REST surface in more detail at docs.protoface.com.


Operational details that matter in production


Interactive signage is a production system, so the auth design needs boring reliability properties:


  • Clock skew tolerance. If your kiosk clock is wrong, short-lived tokens can fail early. Keep token windows reasonable.

  • Retry behavior. Browser reconnects should re-fetch a fresh session rather than reuse a stale one.

  • Scope. Session tokens should authorize one embed, one tenant, or one device class, not your whole account.

  • Observability. Log session creation, connection failures, and expiration reasons on the server so you can distinguish auth bugs from media bugs.


Also separate concerns cleanly: FastAPI decides what is allowed, React renders the UI and manages connection state, and the realtime layer moves audio/video. When those layers are blurred together, security and debugging both get worse.


Conclusion


The safest way to build a locked-down interactive signage avatar is to treat the browser as untrusted, mint only short-lived session credentials on the backend, and keep the realtime media plumbing aligned with your voice agent. FastAPI is a good place to enforce policy and create sessions, React can stay thin, and LiveKit provides the transport layer for synchronized speech and video.


If you want to implement this quickly, start with the docs, pick the integration surface that matches your architecture, and test the failure modes early: token expiry, reconnects, and origin restrictions. For the exact request and response shapes, see docs.protoface.com and the relevant quickstarts in the GitHub org.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.