Multi-tenant apps
Run one app for users who work in more than one Tenant, or in more than one tenant application. Each Context (tenant plus application) has its own conversations, so each gets its own Chat client and its own token.
Prerequisites
Section titled “Prerequisites”chat:access- The token route of your backend and a
getAccessTokenthat calls it. See Get the Chat token in your app. - A user who belongs to the tenant. Chat checks it on every call.
-
Take the tenant from the signed-in user. Use the tenant the user signed in to or picked in your own app, and the application id of the app they are using. Never hard-code a tenant, and never accept one from a URL without checking it against the user’s own memberships. Chat takes the tenant and application only from the request URL and checks them against the Identity Server.
-
Create one client per context.
ChatUserClientandChatNotificationsClienttaketenantIdandapplicationIdin their options and cannot change them. Cache a pair per context, as in the sample. The push client is tenant-level and takes onlytenantId. -
Fetch a token per context. A token issued to your application’s client works only in your application’s context. A call for any other context answers 403
client_context_mismatch. Pass the context to your token route, which exchanges with the backend client of that application. See Issue Chat tokens to your users. -
Switch by replacing the clients. When the user switches tenant or application, disconnect the live connection, drop the old clients and create new ones for the new context.
import { ChatUserClient } from '@central-code-station/ccs-chat-user-client';import { ChatNotificationsClient } from '@central-code-station/ccs-chat-notifications-client';
export interface ChatContextKey { tenantId: string; // from the signed-in user's session, never typed in or guessed applicationId: string;}
const chatApiUrl = 'https://chat.centralcodestation.com/api/v1';const clients = new Map<string, { chat: ChatUserClient; live: ChatNotificationsClient }>();
// Your route returns a Chat token for this context (see "Issue Chat tokens to your users").declare function fetchChatToken(context: ChatContextKey): Promise<string>;
// One pair of clients per context. Switching tenant or application means a new pair.export function chatFor(context: ChatContextKey) { const key = `${context.tenantId}/${context.applicationId}`; let entry = clients.get(key); if (!entry) { const options = { chatApiUrl, ...context, getAccessToken: () => fetchChatToken(context) }; entry = { chat: new ChatUserClient(options), live: new ChatNotificationsClient(options) }; clients.set(key, entry); } return entry;}
// Leaving a context (switching workspace, signing out): stop its live connection and drop it.export function leaveContext(context: ChatContextKey): void { const key = `${context.tenantId}/${context.applicationId}`; clients.get(key)?.live.disconnect(); clients.delete(key);}What Chat guarantees
Section titled “What Chat guarantees”- A user sees data only of the tenant in the URL, and only if they are an active member of it. Otherwise Chat answers 403
not_tenant_memberorno_application_access. - A conversation of another tenant or application is a 404
not_found. Chat does not reveal that it exists. - Presence and web push devices are tenant-level: they use
tenantIdand no application.
Verify
Section titled “Verify”Sign in as a user who belongs to two tenants. In each tenant, list conversations: you see only that tenant’s conversations. Switch tenants in the UI and check that the live connection of the first tenant closes and that no event of the first tenant reaches the second.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause and fix |
|---|---|
403 client_context_mismatch |
The token was issued to a client of another application than the one in the URL. Request the token for the context you call. |
403 not_tenant_member |
The user is not an active member of that tenant. Check their membership in the Identity Server. |
404 application_not_found |
The application does not exist in that tenant, or Chat is not enabled for it. |
| Conversations of the previous tenant stay on screen | The old client is still in use. Replace it when the context changes. |
| Live events keep arriving for the old tenant | The old ChatNotificationsClient was not disconnected. |