Header Logo

How to Secure API Keys for Realtime AI Avatar Customization in a React App

How to Secure API Keys for Realtime AI Avatar Customization in a React App

Secure React avatar apps with backend-for-frontend sessions, ephemeral tokens, key rotation, and rate limiting.

Introduction


If you are putting realtime AI avatars into a React app, the security problem is not the avatar itself. It is where your API keys live, who can mint sessions, and what the browser is allowed to do directly. The common mistake is to wire the frontend straight to a vendor API with a long-lived secret key and call it a day. That works in development and fails the moment your app ships.


In this post, I’ll show the pattern I recommend for securing API keys when your app needs to customize and launch realtime avatars. By the end, you should be able to choose the right trust boundary, keep secrets out of the browser, and set up a React frontend that requests short-lived session credentials from your backend instead of exposing permanent keys.


Start with the trust boundary, not the UI


For a realtime avatar system, the browser is a hostile environment. Even if the app is “internal,” anything shipped to the client can be inspected, replayed, or modified. That means the browser should never receive a long-lived API key that can create avatars, start sessions, or access billing-scoped resources.


The practical rule is simple:


  • Frontend: handles user interaction, form state, and rendering.

  • Backend: holds the API key, calls the avatar API, and returns only narrowly scoped data to the browser.

  • Vendor API: receives authenticated server-to-server calls using your secret key.


For realtime avatars, the sensitive operations are usually things like creating an avatar, creating or initializing a realtime session, or applying customer-specific configuration. Those belong on the server. The browser should get only the minimum data needed to connect to an existing session or display an embed.


What not to do in a React app


The dangerous pattern is embedding something like this in the client bundle:


const API_KEY = "sk_live_...";
});
const API_KEY = "sk_live_...";
});
const API_KEY = "sk_live_...";
});


Once that ships, the key is recoverable from the JavaScript bundle, devtools, network logs, and often error reports. If the key can create sessions or manage avatars, an attacker can do the same. Rotating the key after the fact is annoying and usually means downtime or a rushed migration.


Also avoid putting secrets in:


  • environment variables that are compiled into client code

  • query parameters

  • localStorage or sessionStorage

  • React context or Redux state that is hydrated from the server with secrets intact


A useful mental model: if the browser can see it, assume the user can exfiltrate it.


The secure pattern: backend-for-frontend


The standard fix is a small backend-for-frontend endpoint. Your React app calls your own server, your server authenticates the user, validates the request, then calls the avatar API with the secret key stored server-side. The response sent back to the browser should be short-lived and least-privilege.


In practice, this often looks like:


  1. User selects avatar options in React.

  2. React sends those options to /api/avatar-session on your backend.

  3. Your backend validates the user and the request.

  4. Your backend calls the realtime avatar API with Authorization: Bearer sk_live_....

  5. Your backend returns a session token, signed URL, or other ephemeral connection material to the browser.


That last step is important. The browser should usually receive something that is only useful for one session or a short time window, not the original API key. If your provider supports per-session tokens or signed embeds, use those. If it does not, proxy the minimum necessary request through your server.


A minimal backend example


Here is the shape of a simple server endpoint in Python. The exact request fields depend on the API, so treat this as illustrative and check the docs for the real payload schema.


from fastapi import FastAPI, HTTPException

return r.json()
from fastapi import FastAPI, HTTPException

return r.json()
from fastapi import FastAPI, HTTPException

return r.json()


In a real app, you would validate the authenticated user, restrict which avatar IDs they can use, and redact any fields you do not want exposed to the client. If the vendor returns a session object containing both public and sensitive fields, only forward the public ones.


React-side integration: only consume ephemeral data


On the React side, the app should treat the backend as the source of truth for session startup. That means no direct vendor secret, and ideally no direct vendor REST calls from the browser at all.


import { useEffect, useState } from "react";

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

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

}


Two implementation details matter here.


  • Keep the session short-lived. If your API supports TTLs, use them. If not, wrap the session token in your own server-side expiry.

  • Bind it to the user when possible. A token that can only be used by the authenticated user or originating origin is much harder to abuse.


For React apps that render avatars in an iframe or a video container, the browser may only need a signed embed URL. That is preferable to exposing any backend credential at all.


Rotation, scoping, and rate limiting are not optional


Securing API keys is not just about hiding them. It is also about limiting the blast radius if something leaks.


Use separate keys for development, staging, and production. Keep them in your server secret store, not in source control. Rotate them on a schedule and whenever a machine, CI job, or contractor loses trust. If the provider supports narrower scopes or project-level keys, use the smallest scope that still works.


You should also rate-limit the backend endpoint that mints avatar sessions. Otherwise, an authenticated user can turn your app into a session factory and burn through quota or billing. Even if the vendor enforces its own limits, you still want app-level controls so abuse is caught before it becomes expensive.


Good guardrails for a session-creation endpoint include:


  • authentication and authorization

  • per-user and per-IP rate limits

  • input validation for avatar IDs, voices, and instruction text

  • logging for session creation, but never logging secrets

  • key rotation procedures documented and tested


How this maps to a real Protoface integration


For a React app, the cleanest pattern is to keep the secret on your backend and use the documentation to shape the server-side session request. If you are using the Python SDK, your backend can create avatars or sessions without ever exposing the API key to the browser.


from protoface import Client

)
from protoface import Client

)
from protoface import Client

)


That general model applies whether you launch the avatar from your own React UI, from a LiveKit voice agent, or from a backend workflow. The important part is the same: the browser gets the minimum viable artifact, not the secret.


When an iframe is the better security boundary


Sometimes the right answer is not “how do I secure API keys in React?” but “how do I avoid putting the browser in the trust chain at all?” If you only need a customer-facing avatar widget, a managed iframe embed is often the safer path. The parent page can still control configuration, but the API key never reaches the browser, and the iframe can enforce origin allowlists and rate limits inside the embed boundary.


That is especially useful for marketing sites, support widgets, or lightweight product experiences where you do not need tight in-app orchestration. In those cases, the security posture is simpler because the browser only loads an embed URL; your app does not need to mint API credentials client-side.


Conclusion


If you remember one thing, make it this: never ship a long-lived avatar API key to the browser. Use a backend-for-frontend pattern, return only ephemeral session data, validate and rate-limit session creation, and keep your secret in server-side infrastructure where it belongs.


For implementation details, check docs.protoface.com. If you want a concrete starting point, the quickstarts on GitHub are a good way to see the backend/session boundary in practice. Once the security model is right, the React side stays simple: request a session, render the avatar, and keep the rest of the trust on the server.

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.