Header Logo

Fixing CORS, Auth, and iframe Embed Issues for Realtime Avatars in Angular

Fixing CORS, Auth, and iframe Embed Issues for Realtime Avatars in Angular

Fix Angular realtime avatar embeds: CORS, auth headers, backend session flow, and iframe origin allowlists.

Introduction


When an Angular app needs to embed a realtime avatar, the failure mode is usually not the avatar itself. It is the plumbing around it: cross-origin requests blocked by the browser, auth headers stripped or exposed in the wrong place, and iframe embeds that work in local testing but fail in production because the parent origin is not allowed.


This post walks through the practical issues that show up when you integrate a realtime avatar surface into an Angular app, and how to fix them without weakening your security model. By the end, you should be able to reason about CORS versus same-origin policy, move API-keyed calls to the backend where they belong, and embed an interactive avatar in an Angular frontend with a clean iframe boundary.


CORS is not your auth layer


The first mistake is treating CORS as if it were a permission system. It is not. CORS only tells the browser whether JavaScript running on http://localhost:4200 is allowed to read a response from https://api.protoface.com. It does not authenticate a request, and it does not make an unsafe API safe.


For a developer-facing API, the right pattern is:


  • Use API keys only from your server or from a secure backend function.

  • Return short-lived, least-privilege data to the browser.

  • Keep browser-originated requests away from secret-bearing endpoints.


If your Angular app calls a realtime avatar API directly with Authorization: Bearer sk_live_..., you are already in the wrong trust boundary. Even if the browser allows the request, the key is exposed to anyone with DevTools, extension access, or a compromised client.


What the browser is actually blocking


Angular itself is not special here. The browser enforces same-origin policy, and the most common failure modes look like this:


  • Preflight rejected: your request uses Authorization or another non-simple header, so the browser sends an OPTIONS preflight first. If the API does not respond with the correct Access-Control-Allow-Origin, Access-Control-Allow-Headers, and related headers, the browser blocks the real request.

  • Credential mismatch: you send cookies or credentials, but the server replies with a wildcard origin or no Access-Control-Allow-Credentials.

  • Opaque failure: the server returns a useful error body, but the browser hides it because the CORS handshake never completed.


From the client side, this often looks like a generic “blocked by CORS policy” error in the console. The important point is that the HTTP request may actually have reached the server; the browser is simply refusing to hand the response to your Angular code.


Use a backend boundary for authenticated avatar/session creation


For avatar creation and realtime session management, the clean pattern is to keep API-keyed calls behind your server. Angular should talk to your backend, and your backend should call the avatar API using the secret key. That gives you a place to apply your own authorization, rate limiting, auditing, and input validation.


A minimal backend-to-API flow looks like this:


curl https://api.protoface.com/v1/sessions \
}'
curl https://api.protoface.com/v1/sessions \
}'
curl https://api.protoface.com/v1/sessions \
}'


The exact endpoint and payload fields depend on the feature you are using, so treat the snippet as illustrative. The key idea is that the browser should not see sk_live_... at all.


In Angular, your frontend then calls your own endpoint, for example /api/avatar-session, and your backend returns only the safe result the client needs: perhaps a session identifier, a signed embed URL, or other ephemeral session data.


Angular-specific gotchas: interceptors, environments, and localhost


Angular apps tend to accumulate interceptors quickly. That is useful for your own backend, but it can become a problem when you point the same HttpClient instance at third-party services.


Three practical rules help:


  1. Do not attach your own auth headers to every outbound request blindly. If you have an interceptor that injects JWTs into all requests, make sure it excludes requests to external domains.

  2. Keep API base URLs in environment config. Development often runs on localhost with a dev backend, while production may use a different origin entirely.

  3. Expect preflight during development. Requests from http://localhost:4200 to https://api.protoface.com are cross-origin, so a preflight is normal if you send headers like Authorization.


A typical Angular service should talk to your backend, not directly to the avatar API:


import { HttpClient } from '@angular/common/http';

}
import { HttpClient } from '@angular/common/http';

}
import { HttpClient } from '@angular/common/http';

}


That service is intentionally boring. Boring is good here.


When iframe embeds are the right answer


If the requirement is “put an interactive avatar on any website with no backend and no API key in the browser,” an iframe is the correct boundary. It isolates the avatar runtime, keeps secrets out of the parent page, and makes the security model much easier to reason about.


With a customer-managed iframe embed, the parent page provides the container, but the avatar experience lives inside the iframe. The embed can be configured with parent-origin allowlisting, per-embed voice selection, custom instructions, and rate limits. Those controls matter because they let you expose a useful browser-facing surface without turning the frontend into a secret-bearing client.


The most common iframe problem is not CORS. It is X-Frame-Options or Content-Security-Policy blocking the embed, or a parent-origin mismatch. If the embed only allows specific origins, your Angular app must be served from one of those exact origins, including scheme, host, and port where relevant.


Debugging iframe embed failures in Angular


When an embed does not load, check these layers in order:


  1. Console errors in the parent page. If the browser reports framing restrictions, look for CSP or frame-ancestor issues.

  2. Network request for the iframe URL. A 4xx/5xx response usually indicates an allowlist or session problem, not a rendering bug.

  3. Exact origin string. http://localhost:4200 is not the same as http://127.0.0.1:4200, and https://app.example.com is not the same as https://www.example.com.

  4. Sandboxing attributes. If your wrapper adds restrictive iframe sandbox flags, you can break camera, mic, or postMessage flows.


For Angular specifically, avoid wrapping the iframe in layers that silently change sizing or block user interaction. A responsive container with a stable height is usually enough. Realtime avatar UIs often need enough vertical space to show a face, transcript, and controls without reflowing on every state update.


A practical Python backend example


Here is the shape of a backend helper using the Python SDK: your server receives a request from Angular, calls the avatar/session API with the secret key, and returns a safe response to the browser. The exact method names and fields live in the docs, so keep this as a pattern rather than a copy-paste contract.


from protoface import Client

}
from protoface import Client

}
from protoface import Client

}


If you are integrating with a voice agent stack, the same boundary applies: keep API-keyed operations server-side, and expose only ephemeral identifiers or URLs to Angular.


Where Protoface fits


This is exactly the kind of boundary Protoface is designed for. In practice, you have two reasonable integration modes:


  • Backend-managed sessions through the REST API or Python SDK, where your server creates avatars or sessions and passes safe results to Angular.

  • Customer-managed iframe embeds where the browser never sees an API key, and the embed is constrained by parent-origin allowlists and rate limits.


If you are building a LiveKit voice agent and want the agent to gain a synced talking face, the LiveKit plugin path is the right place to look; if you are embedding in a browser app, the iframe path is usually simpler and more secure. The implementation details and exact request fields are in the docs, and the example repos are useful when you want to see the integration shape end to end.


Common trade-offs and failure modes


There is no one-size-fits-all answer, but the trade-offs are straightforward:


  • Direct browser calls to a secret API are convenient and unsafe.

  • Backend proxying adds a little latency and a lot of control.

  • Iframe embeds reduce integration complexity and isolate secrets, but you need to manage origin allowlists and sizing carefully.

  • WebRTC/realtime surfaces are sensitive to browser permissions, autoplay policies, and network variability, so keep your connection logic robust and your error handling explicit.


One subtle point: if your avatar experience depends on audio autoplay or microphone capture, the browser may require a user gesture before starting playback or opening the mic. Design your Angular UI so the user explicitly clicks “Start” rather than trying to auto-init everything on page load.


Conclusion


If your Angular integration is hitting CORS errors, the fix is usually not “turn CORS off.” The fix is to put the secret-bearing work behind your backend, use the browser only for safe, ephemeral interactions, and choose an iframe boundary when you want a fully browser-native embed without exposing API keys.


For implementation details, session shapes, and the current integration patterns, start with docs.protoface.com. If you need a working example for a particular stack, the linked repos in the docs and quickstarts are the fastest way to validate the full flow before wiring it into your Angular app.

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.