Guide to Multi-Tenant Auth for AI Avatar Interview Practice Platforms in TypeScript

TypeScript guide to tenant-scoped auth, ephemeral session grants, server-side avatar sessions, and billing for AI interview apps
Introduction
Multi-tenant auth for an AI avatar interview practice platform is mostly about one thing: preventing a tenant’s credentials, sessions, and usage from bleeding into another tenant’s experience. In practice, that means your app needs a clean boundary between organizations, interviewers, candidates, and runtime sessions while still letting a realtime avatar service connect, stream, and bill correctly.
This is a good fit for a TypeScript stack because the auth model usually spans your frontend, backend, and third-party realtime providers. By the end of this post, you should be able to design a tenant-aware auth flow for a web app that launches avatar-led interview sessions, keeps API keys server-side, scopes rate limits and billing per tenant, and supports both browser embeds and backend-driven agent sessions.
Start with the trust boundaries, not the avatar
The first mistake teams make is treating the avatar as the product boundary. It is not. The boundary is the tenant. The avatar is just one runtime surface inside a broader system that includes login, authorization, session orchestration, storage, and billing.
For an interview practice platform, typical actors are:
Platform admin: manages all tenants.
Tenant owner: configures organization settings, billing, and avatar behavior.
Interviewer: creates interviews and reviews sessions.
Candidate: joins a practice session, often with a limited, ephemeral link.
The minimum rule set is straightforward:
Every database row that matters must be tenant-scoped.
Every authenticated request must resolve to a tenant context before any business logic runs.
Every realtime session must be linked to exactly one tenant and one authorized purpose.
Any provider credential that can create avatars or sessions must stay on the server.
That sounds obvious, but in realtime systems it is easy to accidentally leak power to the browser. The browser should receive only short-lived, narrowly scoped tokens or URLs, never long-lived API keys.
Model tenancy explicitly in your TypeScript app
Use a tenant identifier everywhere, but do not rely on it alone for security. The tenant ID is a partition key, not an authorization grant. Your auth layer should attach a verified identity and a tenant context to the request.
A common shape looks like this:
Then enforce the tenant boundary at the data layer and the service layer. In other words, every query and every command should filter by tenantId, and every privilege check should confirm the current user is allowed to act on that tenant’s resources.
For example, if an interviewer creates a new practice session, the server should:
Read the authenticated user and tenant from the request.
Verify the user can create sessions for that tenant.
Create a local session record with a tenant-scoped ID.
Call the avatar provider using server-side credentials.
Return only the minimum data needed for the browser to join.
This also means your database schema should avoid shared global tables without tenant constraints. If you use PostgreSQL, tenant-aware indexes are worth the extra ceremony. A composite index on (tenantId, createdAt) or (tenantId, externalSessionId) is usually more useful than a single global index when the platform scales.
Design the browser auth flow around ephemeral session grants
For interview practice, the browser usually does not need direct access to your backend’s API credentials. Instead, the browser should get a short-lived grant to join a specific session. That grant can be a signed token, a one-time session URL, or a backend-minted join credential depending on your transport layer.
The important properties are:
Scoped: valid for one tenant and one interview session.
Short-lived: expires quickly to reduce replay risk.
Non-transferable in practice: tied to a user, session, or origin.
Server-issued: the browser cannot mint its own privilege.
If you are embedding an avatar-driven interview experience into a web app, you often end up with two separate auth mechanisms:
App auth: your own session cookie or JWT identifies the user and tenant.
Realtime join auth: a separate ephemeral token authorizes access to a specific voice/video session.
Keep those distinct. A standard app login should not automatically grant access to every realtime room or every avatar. The join token should encode the room/session identity and be checked by your server before the client connects.
Server-side creation of avatar sessions
The server is where you create avatar sessions, attach interview metadata, and bind the session to tenant billing. The client can request an interview, but the backend should do the actual provider call.
A minimal TypeScript route might look like this:
Notice what is not happening: no API key in the browser, no trust in client-supplied tenant IDs, and no direct client access to provider management endpoints. That pattern matters because interview platforms are easy to abuse with automated session creation, screenshot scraping, or cross-tenant probing.
Rate limits, usage caps, and billing need to be tenant-aware
Interview practice workloads are bursty. One tenant might create a handful of sessions a day; another might run hundreds of concurrent mock interviews during hiring season. Your auth layer should therefore feed your metering layer.
At minimum, track usage per tenant for:
session creation count
session duration
avatar quality tier
concurrent active sessions
Then enforce limits in two places:
Before session creation: block or downgrade requests that exceed quota.
During session runtime: guard against runaway reconnects or repeated session spawning.
This is especially important when the frontend can be opened by candidates with little friction. A candidate link should let them join exactly one interview session, not generate unlimited sessions or call management endpoints. Per-IP and per-duration limits are useful for untrusted, externally facing surfaces. Tenant-level quotas are still required because a single legitimate customer can exceed your intended usage profile.
One practical rule: treat all candidate-facing links as bearer capabilities. If someone has the link, they can join that session until it expires. That is fine as long as the scope is narrow and the backend validates the link against the tenant and interview record.
Where Protoface fits in this architecture
This is the part where Protoface is useful: it gives you a server-side avatar and session API that fits the trust model above. For a backend-driven flow, your TypeScript app can create and manage avatars and realtime sessions from the server, while the browser receives only the join information it needs. The REST API is authenticated with API keys, so the key stays in your backend environment, not in client code.
If you are wiring this into a voice-agent stack, the same principle applies. Your agent runtime can create the conversation, and the avatar becomes the synchronized visual surface for that agent. That keeps the auth story consistent whether the interview is text-first, voice-first, or fully realtime video.
For implementation details and current request shapes, use the documentation. If you want a concrete TypeScript starting point, the Node SDK is the most natural fit for backend session orchestration.
The exact endpoint and payload vary by operation, but the shape is the same: server-side authorization, tenant-scoped resources, and no credential exposure to the browser.
Gotchas that usually show up late
A few failure modes are common enough to call out explicitly:
Tenant ID in the client is not auth. It is just input. Always verify against the logged-in user.
One global API key for all environments is risky. Separate staging and production keys, and rotate them independently.
Sharing provider session IDs across tenants can create confusing audit trails and accidental access paths. Keep them mapped one-to-one to local tenant records.
Long-lived candidate links become support incidents. Expire them aggressively.
No billing guardrails means a single tenant can create expensive realtime load faster than you expect.
Also be careful with origin and embed rules if you support third-party websites. If a tenant can place an avatar on external pages, the embed should enforce an allowlist and mint scoped credentials from your backend. That is the cleanest way to keep the browser from ever seeing management credentials.
Conclusion
Multi-tenant auth for an AI avatar interview platform is mostly disciplined boundary design: verify identity early, scope every resource to a tenant, issue only ephemeral browser grants, and keep provider credentials server-side. Once that is in place, the realtime avatar layer becomes a straightforward runtime detail instead of an auth liability.
If you are building this now, start with a tenant-aware request context, move session creation behind a backend route, and add per-tenant metering before you optimize the realtime experience. Then read the docs and wire up the SDK or plugin that matches your stack.
